From 47fe61ef79573b4373e989d878524c1dea2d72d9 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 4 Dec 2023 18:24:30 +0100 Subject: [PATCH] Introduce lazyTransactionalConnections flag on TransactionAwareDataSourceProxy Includes revision of JDBC transaction tests. Closes gh-29423 --- .../TransactionAwareDataSourceProxy.java | 21 +- .../DataSourceJtaTransactionTests.java | 228 ++- .../DataSourceTransactionManagerTests.java | 73 +- .../support/JdbcTransactionManagerTests.java | 1581 +---------------- 4 files changed, 227 insertions(+), 1676 deletions(-) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy.java index fadc44283fd..7f9a013c621 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy.java @@ -76,6 +76,8 @@ import org.springframework.transaction.support.TransactionSynchronizationManager */ public class TransactionAwareDataSourceProxy extends DelegatingDataSource { + private boolean lazyTransactionalConnections = true; + private boolean reobtainTransactionalConnections = false; @@ -94,6 +96,18 @@ public class TransactionAwareDataSourceProxy extends DelegatingDataSource { super(targetDataSource); } + + /** + * Specify whether to obtain the transactional target Connection lazily on + * actual data access. + *

The default is "true". Specify "false" to immediately obtain a target + * Connection when a transaction-aware Connection handle is retrieved. + * @since 6.1.2 + */ + public void setLazyTransactionalConnections(boolean lazyTransactionalConnections) { + this.lazyTransactionalConnections = lazyTransactionalConnections; + } + /** * Specify whether to reobtain the target Connection for each operation * performed within a transaction. @@ -119,7 +133,12 @@ public class TransactionAwareDataSourceProxy extends DelegatingDataSource { */ @Override public Connection getConnection() throws SQLException { - return getTransactionAwareConnectionProxy(obtainTargetDataSource()); + DataSource ds = obtainTargetDataSource(); + Connection con = getTransactionAwareConnectionProxy(ds); + if (!this.lazyTransactionalConnections && shouldObtainFixedConnection(ds)) { + ((ConnectionProxy) con).getTargetConnection(); + } + return con; } /** diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceJtaTransactionTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceJtaTransactionTests.java index a50c8610c6c..68a2aea470d 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceJtaTransactionTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceJtaTransactionTests.java @@ -73,6 +73,7 @@ public class DataSourceJtaTransactionTests { private Transaction transaction = mock(); + @BeforeEach public void setup() throws Exception { given(dataSource.getConnection()).willReturn(connection); @@ -88,6 +89,7 @@ public class DataSourceJtaTransactionTests { assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isFalse(); } + @Test public void testJtaTransactionCommit() throws Exception { doTestJtaTransaction(false); @@ -110,26 +112,23 @@ public class DataSourceJtaTransactionTests { JtaTransactionManager ptm = new JtaTransactionManager(userTransaction); TransactionTemplate tt = new TransactionTemplate(ptm); - boolean condition3 = !TransactionSynchronizationManager.hasResource(dataSource); - assertThat(condition3).as("Hasn't thread connection").isTrue(); - boolean condition2 = !TransactionSynchronizationManager.isSynchronizationActive(); - assertThat(condition2).as("JTA synchronizations not active").isTrue(); + assertThat(!TransactionSynchronizationManager.hasResource(dataSource)).isTrue(); + assertThat(!TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - boolean condition = !TransactionSynchronizationManager.hasResource(dataSource); - assertThat(condition).as("Hasn't thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("JTA synchronizations active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(!TransactionSynchronizationManager.hasResource(dataSource)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); - Connection c = DataSourceUtils.getConnection(dataSource); - assertThat(TransactionSynchronizationManager.hasResource(dataSource)).as("Has thread connection").isTrue(); - DataSourceUtils.releaseConnection(c, dataSource); + Connection con = DataSourceUtils.getConnection(dataSource); + assertThat(TransactionSynchronizationManager.hasResource(dataSource)).isTrue(); + DataSourceUtils.releaseConnection(con, dataSource); - c = DataSourceUtils.getConnection(dataSource); - assertThat(TransactionSynchronizationManager.hasResource(dataSource)).as("Has thread connection").isTrue(); - DataSourceUtils.releaseConnection(c, dataSource); + con = DataSourceUtils.getConnection(dataSource); + assertThat(TransactionSynchronizationManager.hasResource(dataSource)).isTrue(); + DataSourceUtils.releaseConnection(con, dataSource); if (rollback) { status.setRollbackOnly(); @@ -137,10 +136,8 @@ public class DataSourceJtaTransactionTests { } }); - boolean condition1 = !TransactionSynchronizationManager.hasResource(dataSource); - assertThat(condition1).as("Hasn't thread connection").isTrue(); - boolean condition = !TransactionSynchronizationManager.isSynchronizationActive(); - assertThat(condition).as("JTA synchronizations not active").isTrue(); + assertThat(!TransactionSynchronizationManager.hasResource(dataSource)).isTrue(); + assertThat(!TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); verify(userTransaction).begin(); if (rollback) { verify(userTransaction).rollback(); @@ -220,29 +217,26 @@ public class DataSourceJtaTransactionTests { JtaTransactionManager ptm = new JtaTransactionManager(userTransaction, transactionManager); final TransactionTemplate tt = new TransactionTemplate(ptm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - boolean condition3 = !TransactionSynchronizationManager.hasResource(dsToUse); - assertThat(condition3).as("Hasn't thread connection").isTrue(); - boolean condition2 = !TransactionSynchronizationManager.isSynchronizationActive(); - assertThat(condition2).as("JTA synchronizations not active").isTrue(); + assertThat(!TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(!TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - boolean condition = !TransactionSynchronizationManager.hasResource(dsToUse); - assertThat(condition).as("Hasn't thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("JTA synchronizations active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(!TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); - Connection c = DataSourceUtils.getConnection(dsToUse); + Connection con = DataSourceUtils.getConnection(dsToUse); try { - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - c.isReadOnly(); - DataSourceUtils.releaseConnection(c, dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + con.isReadOnly(); + DataSourceUtils.releaseConnection(con, dsToUse); - c = DataSourceUtils.getConnection(dsToUse); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); + con = DataSourceUtils.getConnection(dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); if (!openOuterConnection) { - DataSourceUtils.releaseConnection(c, dsToUse); + DataSourceUtils.releaseConnection(con, dsToUse); } } catch (SQLException ex) { @@ -253,20 +247,19 @@ public class DataSourceJtaTransactionTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - boolean condition = !TransactionSynchronizationManager.hasResource(dsToUse); - assertThat(condition).as("Hasn't thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("JTA synchronizations active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(!TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); try { - Connection c = DataSourceUtils.getConnection(dsToUse); - c.isReadOnly(); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - DataSourceUtils.releaseConnection(c, dsToUse); - - c = DataSourceUtils.getConnection(dsToUse); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - DataSourceUtils.releaseConnection(c, dsToUse); + Connection con = DataSourceUtils.getConnection(dsToUse); + con.isReadOnly(); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + DataSourceUtils.releaseConnection(con, dsToUse); + + con = DataSourceUtils.getConnection(dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + DataSourceUtils.releaseConnection(con, dsToUse); } catch (SQLException ex) { } @@ -282,15 +275,15 @@ public class DataSourceJtaTransactionTests { if (accessAfterResume) { try { if (!openOuterConnection) { - c = DataSourceUtils.getConnection(dsToUse); + con = DataSourceUtils.getConnection(dsToUse); } - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - c.isReadOnly(); - DataSourceUtils.releaseConnection(c, dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + con.isReadOnly(); + DataSourceUtils.releaseConnection(con, dsToUse); - c = DataSourceUtils.getConnection(dsToUse); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - DataSourceUtils.releaseConnection(c, dsToUse); + con = DataSourceUtils.getConnection(dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + DataSourceUtils.releaseConnection(con, dsToUse); } catch (SQLException ex) { } @@ -298,16 +291,14 @@ public class DataSourceJtaTransactionTests { else { if (openOuterConnection) { - DataSourceUtils.releaseConnection(c, dsToUse); + DataSourceUtils.releaseConnection(con, dsToUse); } } } }); - boolean condition1 = !TransactionSynchronizationManager.hasResource(dsToUse); - assertThat(condition1).as("Hasn't thread connection").isTrue(); - boolean condition = !TransactionSynchronizationManager.isSynchronizationActive(); - assertThat(condition).as("JTA synchronizations not active").isTrue(); + assertThat(!TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(!TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); verify(userTransaction, times(6)).begin(); verify(transactionManager, times(5)).resume(transaction); if (rollback) { @@ -480,31 +471,28 @@ public class DataSourceJtaTransactionTests { JtaTransactionManager ptm = new JtaTransactionManager(userTransaction, transactionManager); final TransactionTemplate tt = new TransactionTemplate(ptm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - boolean condition3 = !TransactionSynchronizationManager.hasResource(dsToUse); - assertThat(condition3).as("Hasn't thread connection").isTrue(); - boolean condition2 = !TransactionSynchronizationManager.isSynchronizationActive(); - assertThat(condition2).as("JTA synchronizations not active").isTrue(); + assertThat(!TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(!TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); assertThatExceptionOfType(TransactionException.class).isThrownBy(() -> tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - boolean condition = !TransactionSynchronizationManager.hasResource(dsToUse); - assertThat(condition).as("Hasn't thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("JTA synchronizations active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); + assertThat(!TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); - Connection c = DataSourceUtils.getConnection(dsToUse); + Connection con = DataSourceUtils.getConnection(dsToUse); try { - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - c.isReadOnly(); - DataSourceUtils.releaseConnection(c, dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + con.isReadOnly(); + DataSourceUtils.releaseConnection(con, dsToUse); - c = DataSourceUtils.getConnection(dsToUse); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); + con = DataSourceUtils.getConnection(dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); if (!openOuterConnection) { - DataSourceUtils.releaseConnection(c, dsToUse); + DataSourceUtils.releaseConnection(con, dsToUse); } } catch (SQLException ex) { @@ -514,26 +502,25 @@ public class DataSourceJtaTransactionTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - boolean condition = !TransactionSynchronizationManager.hasResource(dsToUse); - assertThat(condition).as("Hasn't thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("JTA synchronizations active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - - Connection c = DataSourceUtils.getConnection(dsToUse); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - DataSourceUtils.releaseConnection(c, dsToUse); - - c = DataSourceUtils.getConnection(dsToUse); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - DataSourceUtils.releaseConnection(c, dsToUse); + assertThat(!TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + + Connection con = DataSourceUtils.getConnection(dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + DataSourceUtils.releaseConnection(con, dsToUse); + + con = DataSourceUtils.getConnection(dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + DataSourceUtils.releaseConnection(con, dsToUse); } }); } finally { if (openOuterConnection) { try { - c.isReadOnly(); - DataSourceUtils.releaseConnection(c, dsToUse); + con.isReadOnly(); + DataSourceUtils.releaseConnection(con, dsToUse); } catch (SQLException ex) { } @@ -542,10 +529,8 @@ public class DataSourceJtaTransactionTests { } })); - boolean condition1 = !TransactionSynchronizationManager.hasResource(dsToUse); - assertThat(condition1).as("Hasn't thread connection").isTrue(); - boolean condition = !TransactionSynchronizationManager.isSynchronizationActive(); - assertThat(condition).as("JTA synchronizations not active").isTrue(); + assertThat(!TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(!TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); verify(userTransaction).begin(); if (suspendException) { @@ -586,10 +571,8 @@ public class DataSourceJtaTransactionTests { } }; TransactionTemplate tt = new TransactionTemplate(ptm); - boolean condition2 = !TransactionSynchronizationManager.hasResource(dataSource); - assertThat(condition2).as("Hasn't thread connection").isTrue(); - boolean condition1 = !TransactionSynchronizationManager.isSynchronizationActive(); - assertThat(condition1).as("JTA synchronizations not active").isTrue(); + assertThat(!TransactionSynchronizationManager.hasResource(dataSource)).isTrue(); + assertThat(!TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); given(userTransaction.getStatus()).willReturn(Status.STATUS_ACTIVE); for (int i = 0; i < 3; i++) { @@ -598,31 +581,28 @@ public class DataSourceJtaTransactionTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("JTA synchronizations active").isTrue(); - boolean condition = !status.isNewTransaction(); - assertThat(condition).as("Is existing transaction").isTrue(); + assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); + assertThat(!status.isNewTransaction()).isTrue(); - Connection c = DataSourceUtils.getConnection(dataSource); - assertThat(TransactionSynchronizationManager.hasResource(dataSource)).as("Has thread connection").isTrue(); - DataSourceUtils.releaseConnection(c, dataSource); + Connection con = DataSourceUtils.getConnection(dataSource); + assertThat(TransactionSynchronizationManager.hasResource(dataSource)).isTrue(); + DataSourceUtils.releaseConnection(con, dataSource); - c = DataSourceUtils.getConnection(dataSource); - assertThat(TransactionSynchronizationManager.hasResource(dataSource)).as("Has thread connection").isTrue(); + con = DataSourceUtils.getConnection(dataSource); + assertThat(TransactionSynchronizationManager.hasResource(dataSource)).isTrue(); if (releaseCon) { - DataSourceUtils.releaseConnection(c, dataSource); + DataSourceUtils.releaseConnection(con, dataSource); } } }); if (!releaseCon) { - assertThat(TransactionSynchronizationManager.hasResource(dataSource)).as("Still has connection holder").isTrue(); + assertThat(TransactionSynchronizationManager.hasResource(dataSource)).isTrue(); } else { - boolean condition = !TransactionSynchronizationManager.hasResource(dataSource); - assertThat(condition).as("Hasn't thread connection").isTrue(); + assertThat(!TransactionSynchronizationManager.hasResource(dataSource)).isTrue(); } - boolean condition = !TransactionSynchronizationManager.isSynchronizationActive(); - assertThat(condition).as("JTA synchronizations not active").isTrue(); + assertThat(!TransactionSynchronizationManager.isSynchronizationActive()).isTrue(); } verify(connection, times(3)).close(); } @@ -648,10 +628,10 @@ public class DataSourceJtaTransactionTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - Connection c = DataSourceUtils.getConnection(dsToUse); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - assertThat(c).isSameAs(connection); - DataSourceUtils.releaseConnection(c, dsToUse); + Connection con = DataSourceUtils.getConnection(dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(con).isSameAs(connection); + DataSourceUtils.releaseConnection(con, dsToUse); } }); @@ -660,10 +640,10 @@ public class DataSourceJtaTransactionTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - Connection c = DataSourceUtils.getConnection(dsToUse); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - assertThat(c).isSameAs(connection); - DataSourceUtils.releaseConnection(c, dsToUse); + Connection con = DataSourceUtils.getConnection(dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(con).isSameAs(connection); + DataSourceUtils.releaseConnection(con, dsToUse); } }); @@ -720,10 +700,10 @@ public class DataSourceJtaTransactionTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - Connection c = DataSourceUtils.getConnection(dsToUse); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - assertThat(c).isSameAs(connection1); - DataSourceUtils.releaseConnection(c, dsToUse); + Connection con = DataSourceUtils.getConnection(dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(con).isSameAs(connection1); + DataSourceUtils.releaseConnection(con, dsToUse); } }); @@ -731,10 +711,10 @@ public class DataSourceJtaTransactionTests { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - Connection c = DataSourceUtils.getConnection(dsToUse); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - assertThat(c).isSameAs(connection2); - DataSourceUtils.releaseConnection(c, dsToUse); + Connection con = DataSourceUtils.getConnection(dsToUse); + assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isTrue(); + assertThat(con).isSameAs(connection2); + DataSourceUtils.releaseConnection(con, dsToUse); } }); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java index 7a52e160059..938dadaa3ac 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java @@ -67,20 +67,25 @@ import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING; * @since 04.07.2003 * @see org.springframework.jdbc.support.JdbcTransactionManagerTests */ -public class DataSourceTransactionManagerTests { +public class DataSourceTransactionManagerTests { - private DataSource ds = mock(); + protected DataSource ds = mock(); - private Connection con = mock(); + protected Connection con = mock(); - private DataSourceTransactionManager tm = new DataSourceTransactionManager(ds); + protected DataSourceTransactionManager tm; @BeforeEach public void setup() throws Exception { + tm = createTransactionManager(ds); given(ds.getConnection()).willReturn(con); } + protected DataSourceTransactionManager createTransactionManager(DataSource ds) { + return new DataSourceTransactionManager(ds); + } + @AfterEach public void verifyTransactionSynchronizationManagerState() { assertThat(TransactionSynchronizationManager.getResourceMap()).isEmpty(); @@ -123,18 +128,15 @@ public class DataSourceTransactionManagerTests { private void doTestTransactionCommitRestoringAutoCommit( boolean autoCommit, boolean lazyConnection, final boolean createStatement) throws Exception { + given(con.getAutoCommit()).willReturn(autoCommit); + if (lazyConnection) { - given(con.getAutoCommit()).willReturn(autoCommit); given(con.getTransactionIsolation()).willReturn(Connection.TRANSACTION_READ_COMMITTED); given(con.getWarnings()).willThrow(new SQLException()); } - if (!lazyConnection || createStatement) { - given(con.getAutoCommit()).willReturn(autoCommit); - } - final DataSource dsToUse = (lazyConnection ? new LazyConnectionDataSourceProxy(ds) : ds); - tm = new DataSourceTransactionManager(dsToUse); + tm = createTransactionManager(dsToUse); TransactionTemplate tt = new TransactionTemplate(tm); assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); @@ -213,17 +215,14 @@ public class DataSourceTransactionManagerTests { private void doTestTransactionRollbackRestoringAutoCommit( boolean autoCommit, boolean lazyConnection, final boolean createStatement) throws Exception { + given(con.getAutoCommit()).willReturn(autoCommit); + if (lazyConnection) { - given(con.getAutoCommit()).willReturn(autoCommit); given(con.getTransactionIsolation()).willReturn(Connection.TRANSACTION_READ_COMMITTED); } - if (!lazyConnection || createStatement) { - given(con.getAutoCommit()).willReturn(autoCommit); - } - final DataSource dsToUse = (lazyConnection ? new LazyConnectionDataSourceProxy(ds) : ds); - tm = new DataSourceTransactionManager(dsToUse); + tm = createTransactionManager(dsToUse); TransactionTemplate tt = new TransactionTemplate(tm); assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).isFalse(); assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); @@ -522,7 +521,7 @@ public class DataSourceTransactionManagerTests { @Test public void testParticipatingTransactionWithRollbackOnlyAndInnerSynch() throws Exception { tm.setTransactionSynchronization(DataSourceTransactionManager.SYNCHRONIZATION_NEVER); - DataSourceTransactionManager tm2 = new DataSourceTransactionManager(ds); + DataSourceTransactionManager tm2 = createTransactionManager(ds); // tm has no synch enabled (used at outer level), tm2 has synch enabled (inner level) assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); @@ -613,7 +612,7 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - PlatformTransactionManager tm2 = new DataSourceTransactionManager(ds2); + PlatformTransactionManager tm2 = createTransactionManager(ds2); final TransactionTemplate tt2 = new TransactionTemplate(tm2); tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); @@ -662,7 +661,7 @@ public class DataSourceTransactionManagerTests { final TransactionTemplate tt = new TransactionTemplate(tm); tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - DataSourceTransactionManager tm2 = new DataSourceTransactionManager(ds2); + DataSourceTransactionManager tm2 = createTransactionManager(ds2); tm2.setTransactionSynchronization(DataSourceTransactionManager.SYNCHRONIZATION_NEVER); final TransactionTemplate tt2 = new TransactionTemplate(tm2); tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); @@ -1023,6 +1022,42 @@ public class DataSourceTransactionManagerTests { verify(con).close(); } + @Test + public void testTransactionAwareDataSourceProxyWithLazyFalse() throws Exception { + given(con.getAutoCommit()).willReturn(true); + given(con.getWarnings()).willThrow(new SQLException()); + + TransactionTemplate tt = new TransactionTemplate(tm); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + tt.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + // something transactional + assertThat(DataSourceUtils.getConnection(ds)).isEqualTo(con); + TransactionAwareDataSourceProxy dsProxy = new TransactionAwareDataSourceProxy(ds); + dsProxy.setLazyTransactionalConnections(false); + try { + Connection tCon = dsProxy.getConnection(); + assertThatExceptionOfType(SQLException.class).isThrownBy(tCon::getWarnings); + tCon.clearWarnings(); + assertThat(((ConnectionProxy) dsProxy.getConnection()).getTargetConnection()).isEqualTo(con); + // should be ignored + dsProxy.getConnection().close(); + } + catch (SQLException ex) { + throw new UncategorizedSQLException("", "", ex); + } + } + }); + + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); + InOrder ordered = inOrder(con); + ordered.verify(con).setAutoCommit(false); + ordered.verify(con).commit(); + ordered.verify(con).setAutoCommit(true); + verify(con).close(); + } + @Test public void testTransactionAwareDataSourceProxyWithSuspension() throws Exception { given(con.getAutoCommit()).willReturn(true); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/JdbcTransactionManagerTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/JdbcTransactionManagerTests.java index a33e5df514a..a40a03699c7 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/JdbcTransactionManagerTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/JdbcTransactionManagerTests.java @@ -16,1142 +16,66 @@ package org.springframework.jdbc.support; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; import java.sql.SQLException; -import java.sql.Savepoint; -import java.sql.Statement; import javax.sql.DataSource; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InOrder; -import org.springframework.core.testfixture.EnabledForTestGroups; import org.springframework.dao.ConcurrencyFailureException; -import org.springframework.dao.DataAccessResourceFailureException; -import org.springframework.jdbc.UncategorizedSQLException; -import org.springframework.jdbc.datasource.ConnectionHolder; -import org.springframework.jdbc.datasource.ConnectionProxy; -import org.springframework.jdbc.datasource.DataSourceUtils; -import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy; -import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; -import org.springframework.transaction.CannotCreateTransactionException; -import org.springframework.transaction.IllegalTransactionStateException; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; +import org.springframework.jdbc.datasource.DataSourceTransactionManagerTests; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionSystemException; -import org.springframework.transaction.TransactionTimedOutException; -import org.springframework.transaction.UnexpectedRollbackException; -import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.support.TransactionCallbackWithoutResult; -import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -import static org.assertj.core.api.Assertions.assertThatRuntimeException; -import static org.assertj.core.api.Assertions.fail; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING; /** * @author Juergen Hoeller * @since 5.3 * @see org.springframework.jdbc.datasource.DataSourceTransactionManagerTests */ -public class JdbcTransactionManagerTests { +public class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests { - private DataSource ds = mock(); - - private Connection con = mock(); - - private JdbcTransactionManager tm = new JdbcTransactionManager(ds); - - - @BeforeEach - public void setup() throws Exception { - given(ds.getConnection()).willReturn(con); - } - - @AfterEach - public void verifyTransactionSynchronizationManagerState() { - assertThat(TransactionSynchronizationManager.getResourceMap()).isEmpty(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isFalse(); - } - - - @Test - public void testTransactionCommitWithAutoCommitTrue() throws Exception { - doTestTransactionCommitRestoringAutoCommit(true, false, false); - } - - @Test - public void testTransactionCommitWithAutoCommitFalse() throws Exception { - doTestTransactionCommitRestoringAutoCommit(false, false, false); - } - - @Test - public void testTransactionCommitWithAutoCommitTrueAndLazyConnection() throws Exception { - doTestTransactionCommitRestoringAutoCommit(true, true, false); - } - - @Test - public void testTransactionCommitWithAutoCommitFalseAndLazyConnection() throws Exception { - doTestTransactionCommitRestoringAutoCommit(false, true, false); - } - - @Test - public void testTransactionCommitWithAutoCommitTrueAndLazyConnectionAndStatementCreated() throws Exception { - doTestTransactionCommitRestoringAutoCommit(true, true, true); - } - - @Test - public void testTransactionCommitWithAutoCommitFalseAndLazyConnectionAndStatementCreated() throws Exception { - doTestTransactionCommitRestoringAutoCommit(false, true, true); - } - - private void doTestTransactionCommitRestoringAutoCommit( - boolean autoCommit, boolean lazyConnection, final boolean createStatement) throws Exception { - - if (lazyConnection) { - given(con.getAutoCommit()).willReturn(autoCommit); - given(con.getTransactionIsolation()).willReturn(Connection.TRANSACTION_READ_COMMITTED); - given(con.getWarnings()).willThrow(new SQLException()); - } - - if (!lazyConnection || createStatement) { - given(con.getAutoCommit()).willReturn(autoCommit); - } - - final DataSource dsToUse = (lazyConnection ? new LazyConnectionDataSourceProxy(ds) : ds); - tm = new JdbcTransactionManager(dsToUse); - TransactionTemplate tt = new TransactionTemplate(tm); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - Connection tCon = DataSourceUtils.getConnection(dsToUse); - try { - if (createStatement) { - tCon.createStatement(); - } - else { - tCon.getWarnings(); - tCon.clearWarnings(); - } - } - catch (SQLException ex) { - throw new UncategorizedSQLException("", "", ex); - } - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - if (autoCommit && (!lazyConnection || createStatement)) { - InOrder ordered = inOrder(con); - ordered.verify(con).setAutoCommit(false); - ordered.verify(con).commit(); - ordered.verify(con).setAutoCommit(true); - } - if (createStatement) { - verify(con, times(2)).close(); - } - else { - verify(con).close(); - } - } - - @Test - public void testTransactionRollbackWithAutoCommitTrue() throws Exception { - doTestTransactionRollbackRestoringAutoCommit(true, false, false); - } - - @Test - public void testTransactionRollbackWithAutoCommitFalse() throws Exception { - doTestTransactionRollbackRestoringAutoCommit(false, false, false); - } - - @Test - public void testTransactionRollbackWithAutoCommitTrueAndLazyConnection() throws Exception { - doTestTransactionRollbackRestoringAutoCommit(true, true, false); - } - - @Test - public void testTransactionRollbackWithAutoCommitFalseAndLazyConnection() throws Exception { - doTestTransactionRollbackRestoringAutoCommit(false, true, false); - } - - @Test - public void testTransactionRollbackWithAutoCommitTrueAndLazyConnectionAndCreateStatement() throws Exception { - doTestTransactionRollbackRestoringAutoCommit(true, true, true); - } - - @Test - public void testTransactionRollbackWithAutoCommitFalseAndLazyConnectionAndCreateStatement() throws Exception { - doTestTransactionRollbackRestoringAutoCommit(false, true, true); - } - - private void doTestTransactionRollbackRestoringAutoCommit( - boolean autoCommit, boolean lazyConnection, final boolean createStatement) throws Exception { - - if (lazyConnection) { - given(con.getAutoCommit()).willReturn(autoCommit); - given(con.getTransactionIsolation()).willReturn(Connection.TRANSACTION_READ_COMMITTED); - } - - if (!lazyConnection || createStatement) { - given(con.getAutoCommit()).willReturn(autoCommit); - } - - final DataSource dsToUse = (lazyConnection ? new LazyConnectionDataSourceProxy(ds) : ds); - tm = new JdbcTransactionManager(dsToUse); - TransactionTemplate tt = new TransactionTemplate(tm); - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - final RuntimeException ex = new RuntimeException("Application exception"); - assertThatRuntimeException().isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(dsToUse)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - Connection con = DataSourceUtils.getConnection(dsToUse); - if (createStatement) { - try { - con.createStatement(); - } - catch (SQLException ex) { - throw new UncategorizedSQLException("", "", ex); - } - } - throw ex; - } - })) - .isEqualTo(ex); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - if (autoCommit && (!lazyConnection || createStatement)) { - InOrder ordered = inOrder(con); - ordered.verify(con).setAutoCommit(false); - ordered.verify(con).rollback(); - ordered.verify(con).setAutoCommit(true); - } - if (createStatement) { - verify(con, times(2)).close(); - } - else { - verify(con).close(); - } - } - - @Test - public void testTransactionRollbackOnly() throws Exception { - tm.setTransactionSynchronization(JdbcTransactionManager.SYNCHRONIZATION_NEVER); - TransactionTemplate tt = new TransactionTemplate(tm); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - ConnectionHolder conHolder = new ConnectionHolder(con, true); - TransactionSynchronizationManager.bindResource(ds, conHolder); - final RuntimeException ex = new RuntimeException("Application exception"); - try { - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); - throw ex; - } - }); - fail("Should have thrown RuntimeException"); - } - catch (RuntimeException ex2) { - // expected - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - assertThat(ex2).as("Correct exception thrown").isEqualTo(ex); - } - finally { - TransactionSynchronizationManager.unbindResource(ds); - } - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - } - - @Test - public void testParticipatingTransactionWithRollbackOnly() throws Exception { - doTestParticipatingTransactionWithRollbackOnly(false); - } - - @Test - public void testParticipatingTransactionWithRollbackOnlyAndFailEarly() throws Exception { - doTestParticipatingTransactionWithRollbackOnly(true); - } - - private void doTestParticipatingTransactionWithRollbackOnly(boolean failEarly) throws Exception { - given(con.isReadOnly()).willReturn(false); - if (failEarly) { - tm.setFailEarlyOnGlobalRollbackOnly(true); - } - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition()); - TestTransactionSynchronization synch = - new TestTransactionSynchronization(ds, TransactionSynchronization.STATUS_ROLLED_BACK); - TransactionSynchronizationManager.registerSynchronization(synch); - - boolean outerTransactionBoundaryReached = false; - try { - assertThat(ts.isNewTransaction()).as("Is new transaction").isTrue(); - - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); - assertThat(status.isRollbackOnly()).as("Is not rollback-only").isFalse(); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); - status.setRollbackOnly(); - } - }); - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); - assertThat(status.isRollbackOnly()).as("Is rollback-only").isTrue(); - } - }); - - outerTransactionBoundaryReached = true; - tm.commit(ts); - - fail("Should have thrown UnexpectedRollbackException"); - } - catch (UnexpectedRollbackException ex) { - // expected - if (!outerTransactionBoundaryReached) { - tm.rollback(ts); - } - if (failEarly) { - assertThat(outerTransactionBoundaryReached).isFalse(); - } - else { - assertThat(outerTransactionBoundaryReached).isTrue(); - } - } - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(synch.beforeCommitCalled).isFalse(); - assertThat(synch.beforeCompletionCalled).isTrue(); - assertThat(synch.afterCommitCalled).isFalse(); - assertThat(synch.afterCompletionCalled).isTrue(); - verify(con).rollback(); - verify(con).close(); - } - - @Test - public void testParticipatingTransactionWithIncompatibleIsolationLevel() throws Exception { - tm.setValidateExistingTransaction(true); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - assertThatExceptionOfType(IllegalTransactionStateException.class).isThrownBy(() -> { - final TransactionTemplate tt = new TransactionTemplate(tm); - final TransactionTemplate tt2 = new TransactionTemplate(tm); - tt2.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isRollbackOnly()).as("Is not rollback-only").isFalse(); - tt2.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - status.setRollbackOnly(); - } - }); - assertThat(status.isRollbackOnly()).as("Is rollback-only").isTrue(); - } - }); - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).rollback(); - verify(con).close(); - } - - @Test - public void testParticipatingTransactionWithIncompatibleReadOnly() throws Exception { - willThrow(new SQLException("read-only not supported")).given(con).setReadOnly(true); - tm.setValidateExistingTransaction(true); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - assertThatExceptionOfType(IllegalTransactionStateException.class).isThrownBy(() -> { - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setReadOnly(true); - final TransactionTemplate tt2 = new TransactionTemplate(tm); - tt2.setReadOnly(false); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isRollbackOnly()).as("Is not rollback-only").isFalse(); - tt2.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - status.setRollbackOnly(); - } - }); - assertThat(status.isRollbackOnly()).as("Is rollback-only").isTrue(); - } - }); - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).rollback(); - verify(con).close(); - } - - @Test - public void testParticipatingTransactionWithTransactionStartedFromSynch() throws Exception { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - - final TestTransactionSynchronization synch = - new TestTransactionSynchronization(ds, TransactionSynchronization.STATUS_COMMITTED) { - @Override - protected void doAfterCompletion(int status) { - super.doAfterCompletion(status); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - } - }); - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {}); - } - }; - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - TransactionSynchronizationManager.registerSynchronization(synch); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(synch.beforeCommitCalled).isTrue(); - assertThat(synch.beforeCompletionCalled).isTrue(); - assertThat(synch.afterCommitCalled).isTrue(); - assertThat(synch.afterCompletionCalled).isTrue(); - assertThat(synch.afterCompletionException).isInstanceOf(IllegalStateException.class); - verify(con, times(2)).commit(); - verify(con, times(2)).close(); - } - - @Test - public void testParticipatingTransactionWithDifferentConnectionObtainedFromSynch() throws Exception { - DataSource ds2 = mock(); - final Connection con2 = mock(); - given(ds2.getConnection()).willReturn(con2); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - final TransactionTemplate tt = new TransactionTemplate(tm); - - final TestTransactionSynchronization synch = - new TestTransactionSynchronization(ds, TransactionSynchronization.STATUS_COMMITTED) { - @Override - protected void doAfterCompletion(int status) { - super.doAfterCompletion(status); - Connection con = DataSourceUtils.getConnection(ds2); - DataSourceUtils.releaseConnection(con, ds2); - } - }; - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - TransactionSynchronizationManager.registerSynchronization(synch); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(synch.beforeCommitCalled).isTrue(); - assertThat(synch.beforeCompletionCalled).isTrue(); - assertThat(synch.afterCommitCalled).isTrue(); - assertThat(synch.afterCompletionCalled).isTrue(); - assertThat(synch.afterCompletionException).isNull(); - verify(con).commit(); - verify(con).close(); - verify(con2).close(); - } - - @Test - public void testParticipatingTransactionWithRollbackOnlyAndInnerSynch() throws Exception { - tm.setTransactionSynchronization(JdbcTransactionManager.SYNCHRONIZATION_NEVER); - JdbcTransactionManager tm2 = new JdbcTransactionManager(ds); - // tm has no synch enabled (used at outer level), tm2 has synch enabled (inner level) - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition()); - final TestTransactionSynchronization synch = - new TestTransactionSynchronization(ds, TransactionSynchronization.STATUS_UNKNOWN); - - assertThatExceptionOfType(UnexpectedRollbackException.class).isThrownBy(() -> { - assertThat(ts.isNewTransaction()).as("Is new transaction").isTrue(); - final TransactionTemplate tt = new TransactionTemplate(tm2); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); - assertThat(status.isRollbackOnly()).as("Is not rollback-only").isFalse(); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); - status.setRollbackOnly(); - } - }); - assertThat(status.isNewTransaction()).as("Is existing transaction").isFalse(); - assertThat(status.isRollbackOnly()).as("Is rollback-only").isTrue(); - TransactionSynchronizationManager.registerSynchronization(synch); - } - }); - - tm.commit(ts); - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(synch.beforeCommitCalled).isFalse(); - assertThat(synch.beforeCompletionCalled).isTrue(); - assertThat(synch.afterCommitCalled).isFalse(); - assertThat(synch.afterCompletionCalled).isTrue(); - verify(con).rollback(); - verify(con).close(); - } - - @Test - public void testPropagationRequiresNewWithExistingTransaction() throws Exception { - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - status.setRollbackOnly(); - } - }); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).rollback(); - verify(con).commit(); - verify(con, times(2)).close(); - } - - @Test - public void testPropagationRequiresNewWithExistingTransactionAndUnrelatedDataSource() throws Exception { - Connection con2 = mock(); - final DataSource ds2 = mock(); - given(ds2.getConnection()).willReturn(con2); - - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - - PlatformTransactionManager tm2 = new JdbcTransactionManager(ds2); - final TransactionTemplate tt2 = new TransactionTemplate(tm2); - tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.hasResource(ds2)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - tt2.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - status.setRollbackOnly(); - } - }); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.hasResource(ds2)).as("Hasn't thread connection").isFalse(); - verify(con).commit(); - verify(con).close(); - verify(con2).rollback(); - verify(con2).close(); - } - - @Test - public void testPropagationRequiresNewWithExistingTransactionAndUnrelatedFailingDataSource() throws Exception { - final DataSource ds2 = mock(); - SQLException failure = new SQLException(); - given(ds2.getConnection()).willThrow(failure); - - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - - JdbcTransactionManager tm2 = new JdbcTransactionManager(ds2); - tm2.setTransactionSynchronization(JdbcTransactionManager.SYNCHRONIZATION_NEVER); - final TransactionTemplate tt2 = new TransactionTemplate(tm2); - tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.hasResource(ds2)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - assertThatExceptionOfType(CannotCreateTransactionException.class).isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - tt2.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - status.setRollbackOnly(); - } - }); - } - })).withCause(failure); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.hasResource(ds2)).as("Hasn't thread connection").isFalse(); - verify(con).rollback(); - verify(con).close(); - } - - @Test - public void testPropagationNotSupportedWithExistingTransaction() throws Exception { - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isFalse(); - status.setRollbackOnly(); - } - }); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).commit(); - verify(con).close(); - } - - @Test - public void testPropagationNeverWithExistingTransaction() throws Exception { - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - assertThatExceptionOfType(IllegalTransactionStateException.class).isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - fail("Should have thrown IllegalTransactionStateException"); - } - }); - fail("Should have thrown IllegalTransactionStateException"); - } - })); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).rollback(); - verify(con).close(); - } - - @Test - public void testPropagationSupportsAndRequiresNew() throws Exception { - TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - TransactionTemplate tt2 = new TransactionTemplate(tm); - tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - tt2.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con); - assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con); - } - }); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).commit(); - verify(con).close(); - } - - @Test - public void testPropagationSupportsAndRequiresNewWithEarlyAccess() throws Exception { - final Connection con1 = mock(); - final Connection con2 = mock(); - given(ds.getConnection()).willReturn(con1, con2); - - final - TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con1); - assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con1); - TransactionTemplate tt2 = new TransactionTemplate(tm); - tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - tt2.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con2); - assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con2); - } - }); - assertThat(DataSourceUtils.getConnection(ds)).isSameAs(con1); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con1).close(); - verify(con2).commit(); - verify(con2).close(); - } - - @Test - public void testTransactionWithIsolationAndReadOnly() throws Exception { - given(con.getTransactionIsolation()).willReturn(Connection.TRANSACTION_READ_COMMITTED); - given(con.getAutoCommit()).willReturn(true); - - TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - tt.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); - tt.setReadOnly(true); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isTrue(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - // something transactional - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - InOrder ordered = inOrder(con); - ordered.verify(con).setReadOnly(true); - ordered.verify(con).setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); - ordered.verify(con).setAutoCommit(false); - ordered.verify(con).commit(); - ordered.verify(con).setAutoCommit(true); - ordered.verify(con).setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); - ordered.verify(con).setReadOnly(false); - verify(con).close(); - } - - @Test - public void testTransactionWithEnforceReadOnly() throws Exception { - tm.setEnforceReadOnly(true); - - given(con.getAutoCommit()).willReturn(true); - Statement stmt = mock(); - given(con.createStatement()).willReturn(stmt); - - TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - tt.setReadOnly(true); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isTrue(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isTrue(); - // something transactional - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - InOrder ordered = inOrder(con, stmt); - ordered.verify(con).setReadOnly(true); - ordered.verify(con).setAutoCommit(false); - ordered.verify(stmt).executeUpdate("SET TRANSACTION READ ONLY"); - ordered.verify(stmt).close(); - ordered.verify(con).commit(); - ordered.verify(con).setAutoCommit(true); - ordered.verify(con).setReadOnly(false); - ordered.verify(con).close(); - } - - @ParameterizedTest(name = "transaction with {0} second timeout") - @ValueSource(ints = {1, 10}) - @EnabledForTestGroups(LONG_RUNNING) - public void transactionWithTimeout(int timeout) throws Exception { - PreparedStatement ps = mock(); - given(con.getAutoCommit()).willReturn(true); - given(con.prepareStatement("some SQL statement")).willReturn(ps); - - TransactionTemplate tt = new TransactionTemplate(tm); - tt.setTimeout(timeout); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - - try { - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - try { - Thread.sleep(1500); - } - catch (InterruptedException ex) { - } - try { - Connection con = DataSourceUtils.getConnection(ds); - PreparedStatement ps = con.prepareStatement("some SQL statement"); - DataSourceUtils.applyTransactionTimeout(ps, ds); - } - catch (SQLException ex) { - throw new DataAccessResourceFailureException("", ex); - } - } - }); - if (timeout <= 1) { - fail("Should have thrown TransactionTimedOutException"); - } - } - catch (TransactionTimedOutException ex) { - if (timeout <= 1) { - // expected - } - else { - throw ex; - } - } - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - if (timeout > 1) { - verify(ps).setQueryTimeout(timeout - 1); - verify(con).commit(); - } - else { - verify(con).rollback(); - } - InOrder ordered = inOrder(con); - ordered.verify(con).setAutoCommit(false); - ordered.verify(con).setAutoCommit(true); - verify(con).close(); - } - - @Test - public void testTransactionAwareDataSourceProxy() throws Exception { - given(con.getAutoCommit()).willReturn(true); - given(con.getWarnings()).willThrow(new SQLException()); - - TransactionTemplate tt = new TransactionTemplate(tm); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - // something transactional - assertThat(DataSourceUtils.getConnection(ds)).isEqualTo(con); - TransactionAwareDataSourceProxy dsProxy = new TransactionAwareDataSourceProxy(ds); - try { - Connection tCon = dsProxy.getConnection(); - tCon.getWarnings(); - tCon.clearWarnings(); - assertThat(((ConnectionProxy) dsProxy.getConnection()).getTargetConnection()).isEqualTo(con); - // should be ignored - dsProxy.getConnection().close(); - } - catch (SQLException ex) { - throw new UncategorizedSQLException("", "", ex); - } - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - InOrder ordered = inOrder(con); - ordered.verify(con).setAutoCommit(false); - ordered.verify(con).commit(); - ordered.verify(con).setAutoCommit(true); - verify(con).close(); - } - - @Test - public void testTransactionAwareDataSourceProxyWithSuspension() throws Exception { - given(con.getAutoCommit()).willReturn(true); - - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - // something transactional - assertThat(DataSourceUtils.getConnection(ds)).isEqualTo(con); - final TransactionAwareDataSourceProxy dsProxy = new TransactionAwareDataSourceProxy(ds); - try { - assertThat(((ConnectionProxy) dsProxy.getConnection()).getTargetConnection()).isEqualTo(con); - // should be ignored - dsProxy.getConnection().close(); - } - catch (SQLException ex) { - throw new UncategorizedSQLException("", "", ex); - } - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - // something transactional - assertThat(DataSourceUtils.getConnection(ds)).isEqualTo(con); - try { - assertThat(((ConnectionProxy) dsProxy.getConnection()).getTargetConnection()).isEqualTo(con); - // should be ignored - dsProxy.getConnection().close(); - } - catch (SQLException ex) { - throw new UncategorizedSQLException("", "", ex); - } - } - }); - - try { - assertThat(((ConnectionProxy) dsProxy.getConnection()).getTargetConnection()).isEqualTo(con); - // should be ignored - dsProxy.getConnection().close(); - } - catch (SQLException ex) { - throw new UncategorizedSQLException("", "", ex); - } - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - InOrder ordered = inOrder(con); - ordered.verify(con).setAutoCommit(false); - ordered.verify(con).commit(); - ordered.verify(con).setAutoCommit(true); - verify(con, times(2)).close(); - } - - @Test - public void testTransactionAwareDataSourceProxyWithSuspensionAndReobtaining() throws Exception { - given(con.getAutoCommit()).willReturn(true); - - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - // something transactional - assertThat(DataSourceUtils.getConnection(ds)).isEqualTo(con); - final TransactionAwareDataSourceProxy dsProxy = new TransactionAwareDataSourceProxy(ds); - dsProxy.setReobtainTransactionalConnections(true); - try { - assertThat(((ConnectionProxy) dsProxy.getConnection()).getTargetConnection()).isEqualTo(con); - // should be ignored - dsProxy.getConnection().close(); - } - catch (SQLException ex) { - throw new UncategorizedSQLException("", "", ex); - } - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - // something transactional - assertThat(DataSourceUtils.getConnection(ds)).isEqualTo(con); - try { - assertThat(((ConnectionProxy) dsProxy.getConnection()).getTargetConnection()).isEqualTo(con); - // should be ignored - dsProxy.getConnection().close(); - } - catch (SQLException ex) { - throw new UncategorizedSQLException("", "", ex); - } - } - }); - - try { - assertThat(((ConnectionProxy) dsProxy.getConnection()).getTargetConnection()).isEqualTo(con); - // should be ignored - dsProxy.getConnection().close(); - } - catch (SQLException ex) { - throw new UncategorizedSQLException("", "", ex); - } - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - InOrder ordered = inOrder(con); - ordered.verify(con).setAutoCommit(false); - ordered.verify(con).commit(); - ordered.verify(con).setAutoCommit(true); - verify(con, times(2)).close(); + @Override + protected JdbcTransactionManager createTransactionManager(DataSource ds) { + return new JdbcTransactionManager(ds); } - /** - * Test behavior if the first operation on a connection (getAutoCommit) throws SQLException. - */ - @Test - public void testTransactionWithExceptionOnBegin() throws Exception { - willThrow(new SQLException("Cannot begin")).given(con).getAutoCommit(); - - TransactionTemplate tt = new TransactionTemplate(tm); - assertThatExceptionOfType(CannotCreateTransactionException.class).isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - // something transactional - } - })); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).close(); - } @Test public void testTransactionWithExceptionOnCommit() throws Exception { willThrow(new SQLException("Cannot commit")).given(con).commit(); - TransactionTemplate tt = new TransactionTemplate(tm); + + // plain TransactionSystemException assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { + tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { // something transactional } })); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).close(); } @Test public void testTransactionWithDataAccessExceptionOnCommit() throws Exception { willThrow(new SQLException("Cannot commit")).given(con).commit(); - tm.setExceptionTranslator((task, sql, ex) -> new ConcurrencyFailureException(task)); - + ((JdbcTransactionManager) tm).setExceptionTranslator((task, sql, ex) -> new ConcurrencyFailureException(task)); TransactionTemplate tt = new TransactionTemplate(tm); + + // specific ConcurrencyFailureException assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() -> tt.execute(new TransactionCallbackWithoutResult() { @Override @@ -1160,15 +84,16 @@ public class JdbcTransactionManagerTests { } })); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).close(); } @Test public void testTransactionWithDataAccessExceptionOnCommitFromLazyExceptionTranslator() throws Exception { willThrow(new SQLException("Cannot commit", "40")).given(con).commit(); - TransactionTemplate tt = new TransactionTemplate(tm); + + // specific ConcurrencyFailureException assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() -> tt.execute(new TransactionCallbackWithoutResult() { @Override @@ -1177,7 +102,7 @@ public class JdbcTransactionManagerTests { } })); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).close(); } @@ -1187,15 +112,17 @@ public class JdbcTransactionManagerTests { tm.setRollbackOnCommitFailure(true); TransactionTemplate tt = new TransactionTemplate(tm); + + // plain TransactionSystemException assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { + tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { // something transactional } })); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); verify(con).rollback(); verify(con).close(); } @@ -1204,17 +131,27 @@ public class JdbcTransactionManagerTests { public void testTransactionWithExceptionOnRollback() throws Exception { given(con.getAutoCommit()).willReturn(true); willThrow(new SQLException("Cannot rollback")).given(con).rollback(); - TransactionTemplate tt = new TransactionTemplate(tm); + + // plain TransactionSystemException assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { + tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { + assertThat(status.getTransactionName()).isEmpty(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); status.setRollbackOnly(); + assertThat(status.isRollbackOnly()).isTrue(); + assertThat(status.isCompleted()).isFalse(); } })); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); InOrder ordered = inOrder(con); ordered.verify(con).setAutoCommit(false); ordered.verify(con).rollback(); @@ -1226,9 +163,10 @@ public class JdbcTransactionManagerTests { public void testTransactionWithDataAccessExceptionOnRollback() throws Exception { given(con.getAutoCommit()).willReturn(true); willThrow(new SQLException("Cannot rollback")).given(con).rollback(); - tm.setExceptionTranslator((task, sql, ex) -> new ConcurrencyFailureException(task)); - + ((JdbcTransactionManager) tm).setExceptionTranslator((task, sql, ex) -> new ConcurrencyFailureException(task)); TransactionTemplate tt = new TransactionTemplate(tm); + + // specific ConcurrencyFailureException assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() -> tt.execute(new TransactionCallbackWithoutResult() { @Override @@ -1237,7 +175,7 @@ public class JdbcTransactionManagerTests { } })); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); InOrder ordered = inOrder(con); ordered.verify(con).setAutoCommit(false); ordered.verify(con).rollback(); @@ -1249,17 +187,27 @@ public class JdbcTransactionManagerTests { public void testTransactionWithDataAccessExceptionOnRollbackFromLazyExceptionTranslator() throws Exception { given(con.getAutoCommit()).willReturn(true); willThrow(new SQLException("Cannot rollback", "40")).given(con).rollback(); - TransactionTemplate tt = new TransactionTemplate(tm); + + // specific ConcurrencyFailureException assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() -> tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { + assertThat(status.getTransactionName()).isEmpty(); + assertThat(status.hasTransaction()).isTrue(); + assertThat(status.isNewTransaction()).isTrue(); + assertThat(status.isNested()).isFalse(); + assertThat(status.hasSavepoint()).isFalse(); + assertThat(status.isReadOnly()).isFalse(); + assertThat(status.isRollbackOnly()).isFalse(); status.setRollbackOnly(); + assertThat(status.isRollbackOnly()).isTrue(); + assertThat(status.isCompleted()).isFalse(); } })); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); + assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse(); InOrder ordered = inOrder(con); ordered.verify(con).setAutoCommit(false); ordered.verify(con).rollback(); @@ -1267,435 +215,4 @@ public class JdbcTransactionManagerTests { verify(con).close(); } - @Test - public void testTransactionWithPropagationSupports() throws Exception { - TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(status.isNewTransaction()).as("Is not new transaction").isFalse(); - assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse(); - assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isFalse(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - } - - @Test - public void testTransactionWithPropagationNotSupported() throws Exception { - TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(status.isNewTransaction()).as("Is not new transaction").isFalse(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - } - - @Test - public void testTransactionWithPropagationNever() throws Exception { - TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(status.isNewTransaction()).as("Is not new transaction").isFalse(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - } - - @Test - public void testExistingTransactionWithPropagationNested() throws Exception { - doTestExistingTransactionWithPropagationNested(1); - } - - @Test - public void testExistingTransactionWithPropagationNestedTwice() throws Exception { - doTestExistingTransactionWithPropagationNested(2); - } - - private void doTestExistingTransactionWithPropagationNested(final int count) throws Exception { - DatabaseMetaData md = mock(); - Savepoint sp = mock(); - - given(md.supportsSavepoints()).willReturn(true); - given(con.getMetaData()).willReturn(md); - for (int i = 1; i <= count; i++) { - given(con.setSavepoint(ConnectionHolder.SAVEPOINT_NAME_PREFIX + i)).willReturn(sp); - } - - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active") - .isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); - for (int i = 0; i < count; i++) { - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(status.hasSavepoint()).as("Is nested transaction").isTrue(); - } - }); - } - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con, times(count)).releaseSavepoint(sp); - verify(con).commit(); - verify(con).close(); - } - - @Test - public void testExistingTransactionWithPropagationNestedAndRollback() throws Exception { - DatabaseMetaData md = mock(); - Savepoint sp = mock(); - - given(md.supportsSavepoints()).willReturn(true); - given(con.getMetaData()).willReturn(md); - given(con.setSavepoint("SAVEPOINT_1")).willReturn(sp); - - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active") - .isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(status.hasSavepoint()).as("Is nested transaction").isTrue(); - status.setRollbackOnly(); - } - }); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).rollback(sp); - verify(con).releaseSavepoint(sp); - verify(con).commit(); - verify(con).close(); - } - - @Test - public void testExistingTransactionWithPropagationNestedAndRequiredRollback() throws Exception { - DatabaseMetaData md = mock(); - Savepoint sp = mock(); - - given(md.supportsSavepoints()).willReturn(true); - given(con.getMetaData()).willReturn(md); - given(con.setSavepoint("SAVEPOINT_1")).willReturn(sp); - - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active") - .isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); - assertThatIllegalStateException().isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(status.hasSavepoint()).as("Is nested transaction").isTrue(); - TransactionTemplate ntt = new TransactionTemplate(tm); - ntt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(status.hasSavepoint()).as("Is regular transaction").isFalse(); - throw new IllegalStateException(); - } - }); - } - })); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).rollback(sp); - verify(con).releaseSavepoint(sp); - verify(con).commit(); - verify(con).close(); - } - - @Test - public void testExistingTransactionWithPropagationNestedAndRequiredRollbackOnly() throws Exception { - DatabaseMetaData md = mock(); - Savepoint sp = mock(); - - given(md.supportsSavepoints()).willReturn(true); - given(con.getMetaData()).willReturn(md); - given(con.setSavepoint("SAVEPOINT_1")).willReturn(sp); - - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active") - .isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); - assertThatExceptionOfType(UnexpectedRollbackException.class).isThrownBy(() -> - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(status.hasSavepoint()).as("Is nested transaction").isTrue(); - TransactionTemplate ntt = new TransactionTemplate(tm); - ntt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Has thread connection").isTrue(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization active").isTrue(); - assertThat(status.isNewTransaction()).as("Isn't new transaction").isFalse(); - assertThat(status.hasSavepoint()).as("Is regular transaction").isFalse(); - status.setRollbackOnly(); - } - }); - } - })); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - assertThat(status.hasSavepoint()).as("Isn't nested transaction").isFalse(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).rollback(sp); - verify(con).releaseSavepoint(sp); - verify(con).commit(); - verify(con).close(); - } - - @Test - public void testExistingTransactionWithManualSavepoint() throws Exception { - DatabaseMetaData md = mock(); - Savepoint sp = mock(); - - given(md.supportsSavepoints()).willReturn(true); - given(con.getMetaData()).willReturn(md); - given(con.setSavepoint("SAVEPOINT_1")).willReturn(sp); - - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active") - .isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - Object savepoint = status.createSavepoint(); - status.releaseSavepoint(savepoint); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).releaseSavepoint(sp); - verify(con).commit(); - verify(con).close(); - verify(ds).getConnection(); - } - - @Test - public void testExistingTransactionWithManualSavepointAndRollback() throws Exception { - DatabaseMetaData md = mock(); - Savepoint sp = mock(); - - given(md.supportsSavepoints()).willReturn(true); - given(con.getMetaData()).willReturn(md); - given(con.setSavepoint("SAVEPOINT_1")).willReturn(sp); - - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - Object savepoint = status.createSavepoint(); - status.rollbackToSavepoint(savepoint); - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).rollback(sp); - verify(con).commit(); - verify(con).close(); - } - - @Test - public void testTransactionWithPropagationNested() throws Exception { - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).commit(); - verify(con).close(); - } - - @Test - public void testTransactionWithPropagationNestedAndRollback() throws Exception { - final TransactionTemplate tt = new TransactionTemplate(tm); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED); - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - assertThat(TransactionSynchronizationManager.isSynchronizationActive()).as("Synchronization not active").isFalse(); - - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException { - assertThat(status.isNewTransaction()).as("Is new transaction").isTrue(); - status.setRollbackOnly(); - } - }); - - assertThat(TransactionSynchronizationManager.hasResource(ds)).as("Hasn't thread connection").isFalse(); - verify(con).rollback(); - verify(con).close(); - } - - - private static class TestTransactionSynchronization implements TransactionSynchronization { - - private DataSource dataSource; - - private int status; - - public boolean beforeCommitCalled; - - public boolean beforeCompletionCalled; - - public boolean afterCommitCalled; - - public boolean afterCompletionCalled; - - public Throwable afterCompletionException; - - public TestTransactionSynchronization(DataSource dataSource, int status) { - this.dataSource = dataSource; - this.status = status; - } - - @Override - public void suspend() { - } - - @Override - public void resume() { - } - - @Override - public void flush() { - } - - @Override - public void beforeCommit(boolean readOnly) { - if (this.status != TransactionSynchronization.STATUS_COMMITTED) { - fail("Should never be called"); - } - assertThat(this.beforeCommitCalled).isFalse(); - this.beforeCommitCalled = true; - } - - @Override - public void beforeCompletion() { - assertThat(this.beforeCompletionCalled).isFalse(); - this.beforeCompletionCalled = true; - } - - @Override - public void afterCommit() { - if (this.status != TransactionSynchronization.STATUS_COMMITTED) { - fail("Should never be called"); - } - assertThat(this.afterCommitCalled).isFalse(); - this.afterCommitCalled = true; - } - - @Override - public void afterCompletion(int status) { - try { - doAfterCompletion(status); - } - catch (Throwable ex) { - this.afterCompletionException = ex; - } - } - - protected void doAfterCompletion(int status) { - assertThat(this.afterCompletionCalled).isFalse(); - this.afterCompletionCalled = true; - assertThat(status).isEqualTo(this.status); - assertThat(TransactionSynchronizationManager.hasResource(this.dataSource)).isTrue(); - } - } - }