Поддержка YDB в Jimmer ORM#215
Conversation
Without tests
1. Started using YdbHelperExtension 2. fixed errors in the SQL file and added a check for dropping tables from the SQL file
The test now successfully sends a "select" request to the DB. Next, there needs be a checker for the result and sql script generated
Java version 17 is the minimum Java version to support all Jimmer features.
auto-increment has been replaced by Uuid generated by Java
Changed mapping of Java classes to SQL types to be the same as in the YDB JDBC
| connection.rollback(); | ||
| if (i == maxRetries - 1 || !isRetryable(ex)) { | ||
| throw new RuntimeException(ex); | ||
| } |
There was a problem hiding this comment.
Severity: Major
Confidence: High
If block.apply(connection) throws a RuntimeException (or any unchecked exception), the catch (SQLException) block is skipped, so connection.rollback() is never called. The connection is then closed by try-with-resources, but whether the JDBC driver auto-rolls-back on close() is driver-specific and not guaranteed by the JDBC spec.
This can lead to partially committed data if the YDB driver doesn't auto-rollback on close.
Suggested fix: add a generic catch (Exception ex) block (or a finally that rolls back if not committed), for example:
try {
connection.setAutoCommit(false);
R result = block.apply(connection);
connection.commit();
return result;
} catch (Exception ex) {
connection.rollback();
if (ex instanceof SQLException sqlEx && (i < maxRetries - 1) && isRetryable(sqlEx)) {
// continue retry loop
} else {
throw (ex instanceof RuntimeException re) ? re : new RuntimeException(ex);
}
}| } | ||
|
|
||
| @Override | ||
| public <R> R executeTransaction(Propagation propagation, Function<Connection, R> block) { |
There was a problem hiding this comment.
Severity: Major
Confidence: Medium
The propagation parameter is accepted but completely ignored. Jimmer's TxConnectionManager.executeTransaction contract expects the implementation to respect propagation semantics (REQUIRED, REQUIRES_NEW, NESTED, etc.).
Currently, every call creates a brand-new independent transaction, which is equivalent to REQUIRES_NEW. If a caller passes REQUIRED expecting to join an existing transaction, this implementation silently creates a separate one, which can break transactional consistency.
Suggested fix: either implement propagation support (e.g., using a ThreadLocal to track the current connection/transaction) or document that this class only supports REQUIRES_NEW and throw UnsupportedOperationException for other modes.
| ) { | ||
| sqlClient().getConnectionManager().execute(con -> { | ||
| try { | ||
| try (PreparedStatement stmt = con.prepareStatement(INSERT_WITH_UUID)) { |
There was a problem hiding this comment.
Severity: Major
Confidence: Medium
The batchSize parameter is validated in the constructor (batchSize < 1 throws) and passed to the superclass, but the save() method here batches all keys in a single executeBatch() call regardless of batchSize. Since both delete() and deleteAll() are overridden, the superclass's chunking logic (which would split work into batchSize-sized batches) is bypassed.
For a large collection of cache keys, this could cause memory pressure or exceed YDB's batch statement limits.
Suggested fix: chunk the keys collection into batchSize-sized groups and call executeBatch() for each chunk:
int count = 0;
for (Object key : keys) {
// ... setString calls ...
stmt.addBatch();
if (++count % batchSize == 0) {
stmt.executeBatch();
}
}
if (count % batchSize != 0) {
stmt.executeBatch();
}(where batchSize needs to be stored as an instance field)
| @@ -0,0 +1,17 @@ | |||
| docker run \ | |||
| --restart=always \ | |||
| -d --rm \ | |||
There was a problem hiding this comment.
Severity: Major
Confidence: High
--restart=always and --rm are mutually exclusive Docker flags. On Docker 18.06+ this produces an error:
Conflicting options: --restart and --rm
The script will fail to start the container.
Suggested fix: remove either --rm (if you want the container to persist and auto-restart) or --restart=always (if you want a disposable container that is removed on stop).
| protected static YqlClient getIsolationClient() { | ||
| DataSource dataSource = new DriverManagerDataSource(getJdbcURL()); | ||
| return new YqlClient( | ||
| (JSqlClientImplementor) JSqlClient.newBuilder() |
There was a problem hiding this comment.
Severity: Minor
Confidence: High
getIsolationClient() builds a JSqlClient without calling .setDialect(new YdbDialect()). This means the client returned to tests (used in AbstractTransactionTest, KeysetTest) uses Jimmer's default dialect rather than the YDB dialect.
Currently the tests only execute simple queries where the default dialect happens to produce compatible SQL, but this will silently produce incorrect SQL for any YDB-specific operations (UPSERT, type mappings, etc.).
Suggested fix:
return new YqlClient(
(JSqlClientImplementor) JSqlClient.newBuilder()
.setDialect(new YdbDialect())
.setConnectionManager(new YdbTxConnectionManager(dataSource))
.setExecutor(executor)
.build());| public final class YdbClassMapping { | ||
| private YdbClassMapping() {} | ||
|
|
||
| public static final Map<Class<?>, String> classToYdbType = new HashMap<>(); |
There was a problem hiding this comment.
Severity: Minor
Confidence: Medium
classToYdbType and classToJdbcType are declared as public static final but the concrete type is a mutable HashMap. Any code that has access to these fields can call put(), remove(), or clear() on them, corrupting the shared mapping for all callers.
Suggested fix: wrap them with Collections.unmodifiableMap() after the static initializer block populates a temporary mutable map, or use Map.ofEntries(...) for an immutable map.
| <scope>test</scope> | ||
| </dependency> | ||
| <!-- Source: https://mvnrepository.com/artifact/org.liquibase/liquibase-core --> | ||
| <dependency> |
There was a problem hiding this comment.
Severity: Minor
Confidence: Medium
liquibase-core is declared at compile scope but is not imported or used anywhere in the main source code (src/main/java). This adds a significant transitive dependency tree (~3 MB+) to the artifact for no functional benefit.
If Liquibase is intended for future use, consider changing the scope to provided or test. If it's not needed, remove it.
AI Review SummaryVerdict: ✅ No critical issues found Critical issuesNo critical issues found. Other findings
This review was generated automatically. Critical issues require attention; other findings are advisory. |
Removed "--rm" to mirror the docker-compose.yml file
… the con.setSavepoint() problem YDB JDBC driver doesn't support con.setSavepoint(). That's why isTransactionAbortedByError() in dialect was removed (it now returns false and doen't trigger setSavepoint())
Previously, only select calls were using the variable batchSize. Delete operators are now separated into groups to prevent memory pressure and to not exceed YDB's batch statement limits.
…ctionality in the ConnectionManager
Fixed issues mentioned by the bot review and fixed versioning
Fixed retry logic and added chaos tests
CRUD, scan, join
Добавлены диалект и вспомогательные классы для поддержки YDB в Jimmer ORM.