diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java index cc232cd2f..aaa653c48 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 the original author or authors. + * Copyright 2010-2013 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. @@ -26,14 +26,13 @@ import com.mongodb.DB; import com.mongodb.Mongo; /** - * Helper class featuring helper methods for internal MongoDb classes. - *

- *

- * Mainly intended for internal use within the framework. + * Helper class featuring helper methods for internal MongoDb classes. Mainly intended for internal use within the + * framework. * * @author Thomas Risberg * @author Graeme Rocher * @author Oliver Gierke + * @author Randy Watler * @since 1.0 */ public abstract class MongoDbUtils { @@ -131,8 +130,11 @@ public abstract class MongoDbUtils { holderToUse.addDB(databaseName, db); } - TransactionSynchronizationManager.registerSynchronization(new MongoSynchronization(holderToUse, mongo)); - holderToUse.setSynchronizedWithTransaction(true); + // synchronize holder only if not yet synchronized + if (!holderToUse.isSynchronizedWithTransaction()) { + TransactionSynchronizationManager.registerSynchronization(new MongoSynchronization(holderToUse, mongo)); + holderToUse.setSynchronizedWithTransaction(true); + } if (holderToUse != dbHolder) { TransactionSynchronizationManager.bindResource(mongo, holderToUse); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java index 507680c26..7a67bd959 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2013 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. @@ -20,6 +20,8 @@ import static org.junit.Assert.*; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; +import java.util.List; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -28,7 +30,9 @@ import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.transaction.support.TransactionSynchronizationUtils; import com.mongodb.DB; import com.mongodb.Mongo; @@ -37,12 +41,12 @@ import com.mongodb.Mongo; * Unit tests for {@link MongoDbUtils}. * * @author Oliver Gierke + * @author Randy Watler */ @RunWith(MockitoJUnitRunner.class) public class MongoDbUtilsUnitTests { - @Mock - Mongo mongo; + @Mock Mongo mongo; @Before public void setUp() throws Exception { @@ -81,4 +85,94 @@ public class MongoDbUtilsUnitTests { assertThat(first, is(notNullValue())); assertThat(MongoDbUtils.getDB(mongo, "first"), is(sameInstance(first))); } + + /** + * @see DATAMONGO-737 + */ + @Test + public void handlesTransactionSynchronizationLifecycle() { + + // ensure transaction synchronization manager has no registered + // transaction synchronizations or bound resources at start of test + assertThat(TransactionSynchronizationManager.getSynchronizations().isEmpty(), is(true)); + assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty(), is(true)); + + // access database for one mongo instance, (registers transaction + // synchronization and binds transaction resource) + MongoDbUtils.getDB(mongo, "first"); + + // ensure transaction synchronization manager has registered + // transaction synchronizations and bound resources + assertThat(TransactionSynchronizationManager.getSynchronizations().isEmpty(), is(false)); + assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty(), is(false)); + + // simulate transaction completion, (unbinds transaction resource) + try { + simulateTransactionCompletion(); + } catch (Exception e) { + fail("Unexpected exception thrown during transaction completion: " + e); + } + + // ensure transaction synchronization manager has no bound resources + // at end of test + assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty(), is(true)); + } + + /** + * @see DATAMONGO-737 + */ + @Test + public void handlesTransactionSynchronizationsLifecycle() { + + // ensure transaction synchronization manager has no registered + // transaction synchronizations or bound resources at start of test + assertThat(TransactionSynchronizationManager.getSynchronizations().isEmpty(), is(true)); + assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty(), is(true)); + + // access multiple databases for one mongo instance, (registers + // transaction synchronizations and binds transaction resources) + MongoDbUtils.getDB(mongo, "first"); + MongoDbUtils.getDB(mongo, "second"); + + // ensure transaction synchronization manager has registered + // transaction synchronizations and bound resources + assertThat(TransactionSynchronizationManager.getSynchronizations().isEmpty(), is(false)); + assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty(), is(false)); + + // simulate transaction completion, (unbinds transaction resources) + try { + simulateTransactionCompletion(); + } catch (Exception e) { + fail("Unexpected exception thrown during transaction completion: " + e); + } + + // ensure transaction synchronization manager has no bound + // transaction resources at end of test + assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty(), is(true)); + } + + /** + * Simulate transaction rollback/commit completion protocol on managed transaction synchronizations which will unbind + * managed transaction resources. Does not swallow exceptions for testing purposes. + * + * @see TransactionSynchronizationUtils#triggerBeforeCompletion() + * @see TransactionSynchronizationUtils#triggerAfterCompletion(int) + */ + private void simulateTransactionCompletion() { + + // triggerBeforeCompletion() implementation without swallowed exceptions + List synchronizations = TransactionSynchronizationManager.getSynchronizations(); + for (TransactionSynchronization synchronization : synchronizations) { + synchronization.beforeCompletion(); + } + + // triggerAfterCompletion() implementation without swallowed exceptions + List remainingSynchronizations = TransactionSynchronizationManager + .getSynchronizations(); + if (remainingSynchronizations != null) { + for (TransactionSynchronization remainingSynchronization : remainingSynchronizations) { + remainingSynchronization.afterCompletion(TransactionSynchronization.STATUS_ROLLED_BACK); + } + } + } }