Using @DataMongoTest
currently does not enable auto-configuration for transactions which means that tests for transactional behavior, e.g. data not persisted on transaction rollback, will not work out of the box. Adding @ImportAutoConfiguration(TransactionAutoConfiguration.class)
(or the equivalent reactive variant) makes this work.
Original StackOverflow post: https://stackoverflow.com/questions/60178310/spring-data-mongodb-transactional-isnt-working/60184283?noredirect=1#comment106470903_60184283
Comment From: mp911de
Note that transactions require a ReplicaSet setup. It makes sense to enable transactional interceptors but we should leave the decision, whether a Mongo Transaction manager gets registered up to the user.
Comment From: snicoll
Thanks @mp911de.
@odrotbohm Spring Boot does not currently auto-configure MongoTransactionManager
as far as I can see so I think we need to revisit this a bit. I am not sure adding @Transactional
on @DataMongoTest
is consistent if we don't auto-configure the necessary infrastructure that is required for it to operate properly.
Comment From: spring-projects-issues
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Comment From: odrotbohm
I agree. I guess the question is then two-fold:
- How do we make the difference in bootstrapped setup obvious to users.
@DataJpaTests
includes transactional auto-configuration,@DataMongoTests
doesn't. Shall we add an explicit note about that in the annotation's Javadoc? - What's the most idiomatic to add transactional behavior to the tests? Could we maybe use the availability of a
MongoDbTransactionManager
in theApplicationContext
to automatically enable transactions? That would at least let users properly run into the limitation outlined by Mark which then brings them to the topic they actually need to solve.
WDYT?
Comment From: snicoll
We've discussed this at the team meeting and we decided to do the following:
- Add the necessary auto-configuration so that transaction management can work (transaction interceptors)
- Add a
@DataMongoTest
sample test that configures a transaction manager (with replica set) to showcase and validate the necessary steps to enable this feature
In particular we decided not to:
- Add
@Transactional
to@DataMongoTest
as this is an opt-in behaviour as discussed above and adding it would give the false promise things happen automatically - Call it out in the javadoc of
@DataMongoTest
but check that this is explicitly documented for test slices where transaction happens automatically. We feel that mentioning explicitly features that are not supported out-of-the-box would not be consistent with all the places where we don't do anything and it isn't called out explicitly either.
Comment From: odrotbohm
Nice, thank you everyone!
Comment From: wilkinsona
The hardest part of this is configuring Mongo itself to use a replica set and then waiting for Mongo to have initialised such that it's in a state where a client session can be created. Using embedded Mongo, that looks like this:
@TestConfiguration(proxyBeanMethods = false)
static class MongoCustomizationConfiguration {
private static final String REPLICA_SET_NAME = "rs1";
@Bean
public IMongodConfig embeddedMongoConfiguration(EmbeddedMongoProperties embeddedProperties) throws IOException {
IMongoCmdOptions cmdOptions = new MongoCmdOptionsBuilder().useNoJournal(false).build();
return new MongodConfigBuilder().version(Version.Main.PRODUCTION)
.replication(new Storage(null, REPLICA_SET_NAME, 0)).cmdOptions(cmdOptions)
.stopTimeoutInMillis(60000).build();
}
@Bean
MongoInitializer mongoInitializer(MongoClient client, MongoTemplate template) {
return new MongoInitializer(client, template);
}
static class MongoInitializer implements InitializingBean {
private final MongoClient client;
private final MongoTemplate template;
MongoInitializer(MongoClient client, MongoTemplate template) {
this.client = client;
this.template = template;
}
@Override
public void afterPropertiesSet() throws Exception {
List<ServerDescription> servers = this.client.getClusterDescription().getServerDescriptions();
assertThat(servers).hasSize(1);
ServerAddress address = servers.get(0).getAddress();
BasicDBList members = new BasicDBList();
members.add(new Document("_id", 0).append("host", address.getHost() + ":" + address.getPort()));
Document config = new Document("_id", REPLICA_SET_NAME);
config.put("members", members);
MongoDatabase admin = this.client.getDatabase("admin");
admin.runCommand(new Document("replSetInitiate", config));
Awaitility.await().atMost(Duration.ofMinutes(1)).until(() -> {
try (ClientSession session = this.client.startSession()) {
return true;
}
catch (Exception ex) {
return false;
}
});
this.template.createCollection("exampleDocuments");
}
}
}
MongoInitializer
is responsible for initialising the replica set, waiting for Mongo to be ready for use, and then creating the document. Document creation is done here as it has to be done outside of a transaction.
The IMongodConfig
bean is required as the use of transactions requires the journal to be enabled:
IMongoCmdOptions cmdOptions = new MongoCmdOptionsBuilder().useNoJournal(false).build();
We also increate the stop timeout as Mongo seems to take longer to stop when a replica set has been configured and embedded Mongo's default is too short.
If you had Mongo pre-configured with a replica set via some other means, using transactions will now be pretty straightforward. You add @Transactional
to your tests and provide a transaction manager either in your tests or, more likely, your main application code:
@Bean
MongoTransactionManager mongoTransactionManager(MongoDatabaseFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}