/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jdbc.datasource;
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.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.tests.Assume;
import org.springframework.tests.TestGroup;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
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.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
/**
* @author Juergen Hoeller
* @since 04.07.2003
*/
public class DataSourceTransactionManagerTests {
private DataSource ds;
private Connection con;
private DataSourceTransactionManager tm;
@Before
public void setUp() throws Exception {
ds = mock(DataSource.class);
con = mock(Connection.class);
given(ds.getConnection()).willReturn(con);
tm = new DataSourceTransactionManager(ds);
}
@After
public void verifyTransactionSynchronizationManagerState() {
assertTrue(TransactionSynchronizationManager.getResourceMap().isEmpty());
assertFalse(TransactionSynchronizationManager.isSynchronizationActive());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
}
@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);
}
if (!lazyConnection || createStatement) {
given(con.getAutoCommit()).willReturn(autoCommit);
}
final DataSource dsToUse = (lazyConnection ? new LazyConnectionDataSourceProxy(ds) : ds);
tm = new DataSourceTransactionManager(dsToUse);
TransactionTemplate tt = new TransactionTemplate(tm);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Is new transaction", status.isNewTransaction());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
Connection tCon = DataSourceUtils.getConnection(dsToUse);
try {
if (createStatement) {
tCon.createStatement();
}
}
catch (SQLException ex) {
throw new UncategorizedSQLException("", "", ex);
}
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
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 DataSourceTransactionManager(dsToUse);
TransactionTemplate tt = new TransactionTemplate(tm);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
final RuntimeException ex = new RuntimeException("Application exception");
try {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Is new transaction", status.isNewTransaction());
Connection con = DataSourceUtils.getConnection(dsToUse);
if (createStatement) {
try {
con.createStatement();
}
catch (SQLException ex) {
throw new UncategorizedSQLException("", "", ex);
}
}
throw ex;
}
});
fail("Should have thrown RuntimeException");
}
catch (RuntimeException ex2) {
// expected
assertTrue("Correct exception thrown", ex2.equals(ex));
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
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(DataSourceTransactionManager.SYNCHRONIZATION_NEVER);
TransactionTemplate tt = new TransactionTemplate(tm);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
ConnectionHolder conHolder = new ConnectionHolder(con);
conHolder.setTransactionActive(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 {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Is existing transaction", !status.isNewTransaction());
throw ex;
}
});
fail("Should have thrown RuntimeException");
}
catch (RuntimeException ex2) {
// expected
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
assertEquals("Correct exception thrown", ex, ex2);
}
finally {
TransactionSynchronizationManager.unbindResource(ds);
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
}
@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);
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
TestTransactionSynchronization synch =
new TestTransactionSynchronization(ds, TransactionSynchronization.STATUS_ROLLED_BACK);
TransactionSynchronizationManager.registerSynchronization(synch);
boolean outerTransactionBoundaryReached = false;
try {
assertTrue("Is new transaction", ts.isNewTransaction());
final TransactionTemplate tt = new TransactionTemplate(tm);
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is existing transaction", !status.isNewTransaction());
assertFalse("Is not rollback-only", status.isRollbackOnly());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Is existing transaction", !status.isNewTransaction());
status.setRollbackOnly();
}
});
assertTrue("Is existing transaction", !status.isNewTransaction());
assertTrue("Is rollback-only", status.isRollbackOnly());
}
});
outerTransactionBoundaryReached = true;
tm.commit(ts);
fail("Should have thrown UnexpectedRollbackException");
}
catch (UnexpectedRollbackException ex) {
// expected
if (!outerTransactionBoundaryReached) {
tm.rollback(ts);
}
if (failEarly) {
assertFalse(outerTransactionBoundaryReached);
}
else {
assertTrue(outerTransactionBoundaryReached);
}
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertFalse(synch.beforeCommitCalled);
assertTrue(synch.beforeCompletionCalled);
assertFalse(synch.afterCommitCalled);
assertTrue(synch.afterCompletionCalled);
verify(con).rollback();
verify(con).close();
}
@Test
public void testParticipatingTransactionWithIncompatibleIsolationLevel() throws Exception {
tm.setValidateExistingTransaction(true);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
try {
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 {
assertFalse("Is not rollback-only", status.isRollbackOnly());
tt2.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
status.setRollbackOnly();
}
});
assertTrue("Is rollback-only", status.isRollbackOnly());
}
});
fail("Should have thrown IllegalTransactionStateException");
}
catch (IllegalTransactionStateException ex) {
// expected
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
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);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
try {
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 {
assertFalse("Is not rollback-only", status.isRollbackOnly());
tt2.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
status.setRollbackOnly();
}
});
assertTrue("Is rollback-only", status.isRollbackOnly());
}
});
fail("Should have thrown IllegalTransactionStateException");
}
catch (IllegalTransactionStateException ex) {
// expected
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).rollback();
verify(con).close();
}
@Test
public void testParticipatingTransactionWithTransactionStartedFromSynch() throws Exception {
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
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 TransactionSynchronizationAdapter() {
});
}
};
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
TransactionSynchronizationManager.registerSynchronization(synch);
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue(synch.beforeCommitCalled);
assertTrue(synch.beforeCompletionCalled);
assertTrue(synch.afterCommitCalled);
assertTrue(synch.afterCompletionCalled);
assertTrue(synch.afterCompletionException instanceof IllegalStateException);
verify(con, times(2)).commit();
verify(con, times(2)).close();
}
@Test
public void testParticipatingTransactionWithDifferentConnectionObtainedFromSynch() throws Exception {
DataSource ds2 = mock(DataSource.class);
final Connection con2 = mock(Connection.class);
given(ds2.getConnection()).willReturn(con2);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
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);
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue(synch.beforeCommitCalled);
assertTrue(synch.beforeCompletionCalled);
assertTrue(synch.afterCommitCalled);
assertTrue(synch.afterCompletionCalled);
assertNull(synch.afterCompletionException);
verify(con).commit();
verify(con).close();
verify(con2).close();
}
@Test
public void testParticipatingTransactionWithRollbackOnlyAndInnerSynch() throws Exception {
tm.setTransactionSynchronization(DataSourceTransactionManager.SYNCHRONIZATION_NEVER);
DataSourceTransactionManager tm2 = new DataSourceTransactionManager(ds);
// tm has no synch enabled (used at outer level), tm2 has synch enabled (inner level)
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
final TestTransactionSynchronization synch =
new TestTransactionSynchronization(ds, TransactionSynchronization.STATUS_UNKNOWN);
try {
assertTrue("Is new transaction", ts.isNewTransaction());
final TransactionTemplate tt = new TransactionTemplate(tm2);
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is existing transaction", !status.isNewTransaction());
assertFalse("Is not rollback-only", status.isRollbackOnly());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Is existing transaction", !status.isNewTransaction());
status.setRollbackOnly();
}
});
assertTrue("Is existing transaction", !status.isNewTransaction());
assertTrue("Is rollback-only", status.isRollbackOnly());
TransactionSynchronizationManager.registerSynchronization(synch);
}
});
tm.commit(ts);
fail("Should have thrown UnexpectedRollbackException");
}
catch (UnexpectedRollbackException ex) {
// expected
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertFalse(synch.beforeCommitCalled);
assertTrue(synch.beforeCompletionCalled);
assertFalse(synch.afterCommitCalled);
assertTrue(synch.afterCompletionCalled);
verify(con).rollback();
verify(con).close();
}
@Test
public void testPropagationRequiresNewWithExistingTransaction() throws Exception {
final TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Is new transaction", status.isNewTransaction());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
status.setRollbackOnly();
}
});
assertTrue("Is new transaction", status.isNewTransaction());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).rollback();
verify(con).commit();
verify(con, times(2)).close();
}
@Test
public void testPropagationRequiresNewWithExistingTransactionAndUnrelatedDataSource() throws Exception {
Connection con2 = mock(Connection.class);
final DataSource ds2 = mock(DataSource.class);
given(ds2.getConnection()).willReturn(con2);
final TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
PlatformTransactionManager tm2 = new DataSourceTransactionManager(ds2);
final TransactionTemplate tt2 = new TransactionTemplate(tm2);
tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds2));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
tt2.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Is new transaction", status.isNewTransaction());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
status.setRollbackOnly();
}
});
assertTrue("Is new transaction", status.isNewTransaction());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds2));
verify(con).commit();
verify(con).close();
verify(con2).rollback();
verify(con2).close();
}
@Test
public void testPropagationRequiresNewWithExistingTransactionAndUnrelatedFailingDataSource() throws Exception {
final DataSource ds2 = mock(DataSource.class);
SQLException failure = new SQLException();
given(ds2.getConnection()).willThrow(failure);
final TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
DataSourceTransactionManager tm2 = new DataSourceTransactionManager(ds2);
tm2.setTransactionSynchronization(DataSourceTransactionManager.SYNCHRONIZATION_NEVER);
final TransactionTemplate tt2 = new TransactionTemplate(tm2);
tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds2));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
try {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
tt2.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
status.setRollbackOnly();
}
});
}
});
fail("Should have thrown CannotCreateTransactionException");
}
catch (CannotCreateTransactionException ex) {
assertSame(failure, ex.getCause());
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds2));
verify(con).rollback();
verify(con).close();
}
@Test
public void testPropagationNotSupportedWithExistingTransaction() throws Exception {
final TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Isn't new transaction", !status.isNewTransaction());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
status.setRollbackOnly();
}
});
assertTrue("Is new transaction", status.isNewTransaction());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).commit();
verify(con).close();
}
@Test
public void testPropagationNeverWithExistingTransaction() throws Exception {
final TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
try {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
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");
}
});
}
catch (IllegalTransactionStateException ex) {
// expected
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).rollback();
verify(con).close();
}
@Test
public void testPropagationSupportsAndRequiresNew() throws Exception {
TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
TransactionTemplate tt2 = new TransactionTemplate(tm);
tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
tt2.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Is new transaction", status.isNewTransaction());
assertSame(con, DataSourceUtils.getConnection(ds));
assertSame(con, DataSourceUtils.getConnection(ds));
}
});
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).commit();
verify(con).close();
}
@Test
public void testPropagationSupportsAndRequiresNewWithEarlyAccess() throws Exception {
final Connection con1 = mock(Connection.class);
final Connection con2 = mock(Connection.class);
given(ds.getConnection()).willReturn(con1, con2);
final
TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertSame(con1, DataSourceUtils.getConnection(ds));
assertSame(con1, DataSourceUtils.getConnection(ds));
TransactionTemplate tt2 = new TransactionTemplate(tm);
tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
tt2.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Is new transaction", status.isNewTransaction());
assertSame(con2, DataSourceUtils.getConnection(ds));
assertSame(con2, DataSourceUtils.getConnection(ds));
}
});
assertSame(con1, DataSourceUtils.getConnection(ds));
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
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);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
assertTrue(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
// something transactional
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
InOrder ordered = inOrder(con);
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);
verify(con).close();
}
@Test
public void testTransactionWithEnforceReadOnly() throws Exception {
tm.setEnforceReadOnly(true);
given(con.getAutoCommit()).willReturn(true);
Statement stmt = mock(Statement.class);
given(con.createStatement()).willReturn(stmt);
TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
tt.setReadOnly(true);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
assertTrue(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
// something transactional
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
InOrder ordered = inOrder(con, stmt);
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).close();
}
@Test
public void testTransactionWithLongTimeout() throws Exception {
doTestTransactionWithTimeout(10);
}
@Test
public void testTransactionWithShortTimeout() throws Exception {
doTestTransactionWithTimeout(1);
}
private void doTestTransactionWithTimeout(int timeout) throws Exception {
Assume.group(TestGroup.PERFORMANCE);
PreparedStatement ps = mock(PreparedStatement.class);
given(con.getAutoCommit()).willReturn(true);
given(con.prepareStatement("some SQL statement")).willReturn(ps);
TransactionTemplate tt = new TransactionTemplate(tm);
tt.setTimeout(timeout);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
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;
}
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
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);
TransactionTemplate tt = new TransactionTemplate(tm);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional
assertEquals(con, DataSourceUtils.getConnection(ds));
TransactionAwareDataSourceProxy dsProxy = new TransactionAwareDataSourceProxy(ds);
try {
assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
// should be ignored
dsProxy.getConnection().close();
}
catch (SQLException ex) {
throw new UncategorizedSQLException("", "", ex);
}
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
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);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional
assertEquals(con, DataSourceUtils.getConnection(ds));
final TransactionAwareDataSourceProxy dsProxy = new TransactionAwareDataSourceProxy(ds);
try {
assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
// 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
assertEquals(con, DataSourceUtils.getConnection(ds));
try {
assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
// should be ignored
dsProxy.getConnection().close();
}
catch (SQLException ex) {
throw new UncategorizedSQLException("", "", ex);
}
}
});
try {
assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
// should be ignored
dsProxy.getConnection().close();
}
catch (SQLException ex) {
throw new UncategorizedSQLException("", "", ex);
}
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
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);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional
assertEquals(con, DataSourceUtils.getConnection(ds));
final TransactionAwareDataSourceProxy dsProxy = new TransactionAwareDataSourceProxy(ds);
dsProxy.setReobtainTransactionalConnections(true);
try {
assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
// 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
assertEquals(con, DataSourceUtils.getConnection(ds));
try {
assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
// should be ignored
dsProxy.getConnection().close();
}
catch (SQLException ex) {
throw new UncategorizedSQLException("", "", ex);
}
}
});
try {
assertEquals(con, ((ConnectionProxy) dsProxy.getConnection()).getTargetConnection());
// should be ignored
dsProxy.getConnection().close();
}
catch (SQLException ex) {
throw new UncategorizedSQLException("", "", ex);
}
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
InOrder ordered = inOrder(con);
ordered.verify(con).setAutoCommit(false);
ordered.verify(con).commit();
ordered.verify(con).setAutoCommit(true);
verify(con, times(2)).close();
}
/**
* 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);
try {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional
}
});
fail("Should have thrown CannotCreateTransactionException");
}
catch (CannotCreateTransactionException ex) {
// expected
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).close();
}
@Test
public void testTransactionWithExceptionOnCommit() throws Exception {
willThrow(new SQLException("Cannot commit")).given(con).commit();
TransactionTemplate tt = new TransactionTemplate(tm);
try {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional
}
});
fail("Should have thrown TransactionSystemException");
}
catch (TransactionSystemException ex) {
// expected
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).close();
}
@Test
public void testTransactionWithExceptionOnCommitAndRollbackOnCommitFailure() throws Exception {
willThrow(new SQLException("Cannot commit")).given(con).commit();
tm.setRollbackOnCommitFailure(true);
TransactionTemplate tt = new TransactionTemplate(tm);
try {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional
}
});
fail("Should have thrown TransactionSystemException");
}
catch (TransactionSystemException ex) {
// expected
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).rollback();
verify(con).close();
}
@Test
public void testTransactionWithExceptionOnRollback() throws Exception {
given(con.getAutoCommit()).willReturn(true);
willThrow(new SQLException("Cannot rollback")).given(con).rollback();
TransactionTemplate tt = new TransactionTemplate(tm);
try {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
status.setRollbackOnly();
}
});
fail("Should have thrown TransactionSystemException");
}
catch (TransactionSystemException ex) {
// expected
}
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
InOrder ordered = inOrder(con);
ordered.verify(con).setAutoCommit(false);
ordered.verify(con).rollback();
ordered.verify(con).setAutoCommit(true);
verify(con).close();
}
@Test
public void testTransactionWithPropagationSupports() throws Exception {
TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Is not new transaction", !status.isNewTransaction());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
}
@Test public void testTransactionWithPropagationNotSupported() throws Exception {
TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Is not new transaction", !status.isNewTransaction());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
}
@Test
public void testTransactionWithPropagationNever() throws Exception {
TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Is not new transaction", !status.isNewTransaction());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
}
@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(DatabaseMetaData.class);
Savepoint sp = mock(Savepoint.class);
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);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
assertTrue("Isn't nested transaction", !status.hasSavepoint());
for (int i = 0; i < count; i++) {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Isn't new transaction", !status.isNewTransaction());
assertTrue("Is nested transaction", status.hasSavepoint());
}
});
}
assertTrue("Is new transaction", status.isNewTransaction());
assertTrue("Isn't nested transaction", !status.hasSavepoint());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con, times(count)).releaseSavepoint(sp);
verify(con).commit();
verify(con).close();
}
@Test
public void testExistingTransactionWithPropagationNestedAndRollback() throws Exception {
DatabaseMetaData md = mock(DatabaseMetaData.class);
Savepoint sp = mock(Savepoint.class);
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);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
assertTrue("Isn't nested transaction", !status.hasSavepoint());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Isn't new transaction", !status.isNewTransaction());
assertTrue("Is nested transaction", status.hasSavepoint());
status.setRollbackOnly();
}
});
assertTrue("Is new transaction", status.isNewTransaction());
assertTrue("Isn't nested transaction", !status.hasSavepoint());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).rollback(sp);
verify(con).releaseSavepoint(sp);
verify(con).commit();
verify(con).isReadOnly();
verify(con).close();
}
@Test
public void testExistingTransactionWithPropagationNestedAndRequiredRollback() throws Exception {
DatabaseMetaData md = mock(DatabaseMetaData.class);
Savepoint sp = mock(Savepoint.class);
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);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
assertTrue("Isn't nested transaction", !status.hasSavepoint());
try {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Isn't new transaction", !status.isNewTransaction());
assertTrue("Is nested transaction", status.hasSavepoint());
TransactionTemplate ntt = new TransactionTemplate(tm);
ntt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Isn't new transaction", !status.isNewTransaction());
assertTrue("Is regular transaction", !status.hasSavepoint());
throw new IllegalStateException();
}
});
}
});
fail("Should have thrown IllegalStateException");
}
catch (IllegalStateException ex) {
// expected
}
assertTrue("Is new transaction", status.isNewTransaction());
assertTrue("Isn't nested transaction", !status.hasSavepoint());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).rollback(sp);
verify(con).releaseSavepoint(sp);
verify(con).commit();
verify(con).isReadOnly();
verify(con).close();
}
@Test
public void testExistingTransactionWithPropagationNestedAndRequiredRollbackOnly() throws Exception {
DatabaseMetaData md = mock(DatabaseMetaData.class);
Savepoint sp = mock(Savepoint.class);
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);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
assertTrue("Isn't nested transaction", !status.hasSavepoint());
try {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Isn't new transaction", !status.isNewTransaction());
assertTrue("Is nested transaction", status.hasSavepoint());
TransactionTemplate ntt = new TransactionTemplate(tm);
ntt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization active", TransactionSynchronizationManager.isSynchronizationActive());
assertTrue("Isn't new transaction", !status.isNewTransaction());
assertTrue("Is regular transaction", !status.hasSavepoint());
status.setRollbackOnly();
}
});
}
});
fail("Should have thrown UnexpectedRollbackException");
}
catch (UnexpectedRollbackException ex) {
// expected
}
assertTrue("Is new transaction", status.isNewTransaction());
assertTrue("Isn't nested transaction", !status.hasSavepoint());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).rollback(sp);
verify(con).releaseSavepoint(sp);
verify(con).commit();
verify(con).isReadOnly();
verify(con).close();
}
@Test
public void testExistingTransactionWithManualSavepoint() throws Exception {
DatabaseMetaData md = mock(DatabaseMetaData.class);
Savepoint sp = mock(Savepoint.class);
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);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
Object savepoint = status.createSavepoint();
status.releaseSavepoint(savepoint);
assertTrue("Is new transaction", status.isNewTransaction());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).releaseSavepoint(sp);
verify(con).commit();
verify(con).close();
verify(ds).getConnection();
}
@Test
public void testExistingTransactionWithManualSavepointAndRollback() throws Exception {
DatabaseMetaData md = mock(DatabaseMetaData.class);
Savepoint sp = mock(Savepoint.class);
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);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
Object savepoint = status.createSavepoint();
status.rollbackToSavepoint(savepoint);
assertTrue("Is new transaction", status.isNewTransaction());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
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);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
verify(con).commit();
verify(con).close();
}
@Test
public void testTransactionWithPropagationNestedAndRollback() throws Exception {
final TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
assertTrue("Synchronization not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertTrue("Is new transaction", status.isNewTransaction());
status.setRollbackOnly();
}
});
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
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");
}
assertFalse(this.beforeCommitCalled);
this.beforeCommitCalled = true;
}
@Override
public void beforeCompletion() {
assertFalse(this.beforeCompletionCalled);
this.beforeCompletionCalled = true;
}
@Override
public void afterCommit() {
if (this.status != TransactionSynchronization.STATUS_COMMITTED) {
fail("Should never be called");
}
assertFalse(this.afterCommitCalled);
this.afterCommitCalled = true;
}
@Override
public void afterCompletion(int status) {
try {
doAfterCompletion(status);
}
catch (Throwable ex) {
this.afterCompletionException = ex;
}
}
protected void doAfterCompletion(int status) {
assertFalse(this.afterCompletionCalled);
this.afterCompletionCalled = true;
assertTrue(status == this.status);
assertTrue(TransactionSynchronizationManager.hasResource(this.dataSource));
}
}
}