@@ -91,6 +91,50 @@ public void nestedFailureCaughtByCallerStillRollsBackOuterTransaction() {
9191 .get (0 ).get ("total" )).intValue ());
9292 }
9393
94+ @ Test
95+ public void caughtJdbcWriteFailureStillRollsBackOuterTransaction () {
96+ assertThrows (IllegalStateException .class , () -> sqlExecutor .executeInTransaction (() -> {
97+ sqlExecutor .update ("INSERT INTO test_user (id, name, age) VALUES (?, ?, ?)" ,
98+ new Object [] {94 , "first write" , 20 });
99+ try {
100+ sqlExecutor .update ("INSERT INTO test_user (id, name, age) VALUES (?, ?, ?)" ,
101+ new Object [] {94 , "duplicate key" , 21 });
102+ } catch (RuntimeException expected ) {
103+ // A caller may handle a low-level JDBC failure before returning.
104+ }
105+ }));
106+
107+ assertEquals (0 , ((Number ) sqlExecutor .queryForList (
108+ "SELECT count(*) AS total FROM test_user" , new Object [0 ])
109+ .get (0 ).get ("total" )).intValue ());
110+ }
111+
112+ @ Test
113+ public void caughtStatementOrReadFailureCannotCommitEarlierWrites () {
114+ List <Runnable > failures = List .of (
115+ () -> sqlExecutor .execute ("INSERT INTO test_user (id, name, age) VALUES (94, 'duplicate', 21)" ),
116+ () -> sqlExecutor .batchUpdate (
117+ "INSERT INTO test_user (id, name, age) VALUES (?, ?, ?)" ,
118+ java .util .Collections .singletonList (new Object [] {94 , "duplicate" , 21 })),
119+ () -> sqlExecutor .queryForList ("SELECT * FROM missing_test_table" , new Object [0 ]),
120+ () -> sqlExecutor .query ("SELECT * FROM missing_test_table" , new Object [0 ],
121+ row -> new TypedRow ()));
122+ for (Runnable failedStatement : failures ) {
123+ assertThrows (IllegalStateException .class , () -> sqlExecutor .executeInTransaction (() -> {
124+ sqlExecutor .update ("INSERT INTO test_user (id, name, age) VALUES (?, ?, ?)" ,
125+ new Object [] {94 , "must roll back" , 20 });
126+ try {
127+ failedStatement .run ();
128+ } catch (RuntimeException expected ) {
129+ // The failed statement must poison the shared transaction.
130+ }
131+ }));
132+ assertEquals (0 , ((Number ) sqlExecutor .queryForList (
133+ "SELECT count(*) AS total FROM test_user" , new Object [0 ])
134+ .get (0 ).get ("total" )).intValue ());
135+ }
136+ }
137+
94138 @ Test
95139 public void testQueryForListNormalizesAliasesForPortableMapping () {
96140 List <Map <String , Object >> rows = sqlExecutor .queryForList (
0 commit comments