Skip to content

Commit 54d0f11

Browse files
author
Philip Z
committed
Rollback JDBC transaction after caught mapper failures
1 parent b81d975 commit 54d0f11

2 files changed

Lines changed: 37 additions & 4 deletions

File tree

‎teaql-provider-jdbc/src/main/java/io/teaql/provider/jdbc/JdbcSqlExecutor.java‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ public List<Map<String, Object>> queryForList(String sql, Object[] params) {
186186
} catch (SQLException e) {
187187
markTransactionRollbackOnly();
188188
throw new RuntimeException("JDBC query failed for parameterized SQL: " + sql, e);
189+
} catch (RuntimeException | Error failure) {
190+
markTransactionRollbackOnly();
191+
throw failure;
189192
} finally {
190193
if (owned && connection != null) try { connection.close(); } catch (SQLException ignored) { }
191194
}
@@ -213,6 +216,9 @@ public <T extends io.teaql.core.Entity> List<T> query(
213216
} catch (SQLException exception) {
214217
markTransactionRollbackOnly();
215218
throw new RuntimeException("JDBC typed query failed for parameterized SQL: " + sql, exception);
219+
} catch (RuntimeException | Error failure) {
220+
markTransactionRollbackOnly();
221+
throw failure;
216222
} finally {
217223
if (owned && connection != null) try { connection.close(); } catch (SQLException ignored) { }
218224
}
@@ -343,6 +349,9 @@ public void execute(String sql) {
343349
} catch (SQLException e) {
344350
markTransactionRollbackOnly();
345351
throw new RuntimeException(e);
352+
} catch (RuntimeException | Error failure) {
353+
markTransactionRollbackOnly();
354+
throw failure;
346355
} finally {
347356
if (owned && connection != null) try { connection.close(); } catch (SQLException ignored) { }
348357
}
@@ -369,6 +378,9 @@ public int update(String sql, Object[] params) {
369378
} catch (SQLException e) {
370379
markTransactionRollbackOnly();
371380
throw new RuntimeException(e);
381+
} catch (RuntimeException | Error failure) {
382+
markTransactionRollbackOnly();
383+
throw failure;
372384
} finally {
373385
if (owned && connection != null) try { connection.close(); } catch (SQLException ignored) { }
374386
}
@@ -393,6 +405,9 @@ public int[] batchUpdate(String sql, List<Object[]> paramsList) {
393405
} catch (SQLException e) {
394406
markTransactionRollbackOnly();
395407
throw new RuntimeException(e);
408+
} catch (RuntimeException | Error failure) {
409+
markTransactionRollbackOnly();
410+
throw failure;
396411
} finally {
397412
if (owned && connection != null) try { connection.close(); } catch (SQLException ignored) { }
398413
}
@@ -421,7 +436,7 @@ public void executeInTransaction(Runnable action) {
421436
action.run();
422437
if (Boolean.TRUE.equals(transactionRollbackOnly.get())) {
423438
throw new IllegalStateException(
424-
"JDBC transaction is rollback-only after a nested failure");
439+
"JDBC transaction is rollback-only after a failed operation");
425440
}
426441
connection.commit();
427442
} catch (RuntimeException | Error failure) {
@@ -438,9 +453,9 @@ public void executeInTransaction(Runnable action) {
438453
}
439454

440455
private void markTransactionRollbackOnly() {
441-
// A failed statement may have left earlier writes in the shared
442-
// transaction. Without a savepoint, catching that failure cannot
443-
// make the transaction safe to commit.
456+
// A failed statement, mapper or adapter operation may have left earlier
457+
// writes in the shared transaction. Without a savepoint, catching the
458+
// failure cannot make the transaction safe to commit.
444459
if (transactionConnection.get() != null) transactionRollbackOnly.set(true);
445460
}
446461

‎teaql-provider-jdbc/src/test/java/io/teaql/provider/jdbc/JdbcSqlExecutorTest.java‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,24 @@ public void caughtStatementOrReadFailureCannotCommitEarlierWrites() {
135135
}
136136
}
137137

138+
@Test
139+
public void caughtTypedRowMapperFailureCannotCommitEarlierWrites() {
140+
assertThrows(IllegalStateException.class, () -> sqlExecutor.executeInTransaction(() -> {
141+
sqlExecutor.update("INSERT INTO test_user (id, name, age) VALUES (?, ?, ?)",
142+
new Object[] {95, "must roll back", 20});
143+
try {
144+
sqlExecutor.query("SELECT id FROM test_user WHERE id = ?", new Object[] {95},
145+
row -> { throw new IllegalArgumentException("bad typed row"); });
146+
} catch (IllegalArgumentException expected) {
147+
// The mapper failure escaped a query using the same transaction.
148+
}
149+
}));
150+
151+
assertEquals(0, ((Number) sqlExecutor.queryForList(
152+
"SELECT count(*) AS total FROM test_user", new Object[0])
153+
.get(0).get("total")).intValue());
154+
}
155+
138156
@Test
139157
public void testQueryForListNormalizesAliasesForPortableMapping() {
140158
List<Map<String, Object>> rows = sqlExecutor.queryForList(

0 commit comments

Comments
 (0)