spring-boot-mongodb-transaction

Today, I need to use the tansaction of mongodb in my work, so I consulted various materials and stepped on a lot of pits. Here is a summary.

Premise

  • The version of MongoDB must be version 4.0 or above
  • Introduce spring-data

Configure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
@EnableMongoRepositories(basePackages = "com.baeldung.repository")
public class MongoTransactionConfig extends AbstractMongoConfiguration{

@Bean
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}

@Override
protected String getDatabaseName() {
return "test";
}

@Override
public MongoClient mongoClient() {
return new MongoClient("127.0.0.1", 27017);
}
}

After we finished the configuration, all we need to do to use native MongoDB transactions – is to annotate our method with @Transactional.

Everything inside the annotated method will be executed in one transaction:

1
2
3
4
5
6
7
8
9
10
@Test
@Transactional
public void whenPerformMongoTransaction_thenSuccess() {
userRepository.save(new User("John", 30));
userRepository.save(new User("Ringo", 35));
Query query = new Query().addCriteria(Criteria.where("name").is("John"));
List<User> users = mongoTemplate.find(query, User.class);

assertThat(users.size(), is(1));
}

Note that we can’t use listCollections command inside a multi-document transaction – for example:

1
2
3
4
5
6
7
8
@Test(expected = MongoTransactionException.class)
@Transactional
public void whenListCollectionDuringMongoTransaction_thenException() {
if (mongoTemplate.collectionExists(User.class)) {
mongoTemplate.save(new User("John", 30));
mongoTemplate.save(new User("Ringo", 35));
}
}

This example throws a MongoTransactionException as we used the collectionExists() method.

We also can’t run count inside a multi-document transaction:

1
2
3
4
5
6
7
@Test(expected = MongoCommandException.class)
@Transactional
public void whenCountDuringMongoTransaction_thenException() {
userRepository.save(new User("John", 30));
userRepository.save(new User("Ringo", 35));
userRepository.count();
}

But, we can work around this one with a simple query and then get the size of the resulting list:

1
2
3
4
5
6
7
8
9
@Test
@Transactional
public void whenQueryDuringMongoTransaction_thenSuccess() {
userRepository.save(new User("Jane", 20));
userRepository.save(new User("Nick", 33));
List<User> users = mongoTemplate.find(new Query(), User.class);

assertTrue(users.size() > 1);
}

Precautions

  • Annotation must be @Transactional (rollbackFor = {Exception.class}), not just @Transactional

  • Replica-set must be configured in the properties file, otherwise the error “Sessions are not supported by the MongoDB cluster to which this client is connected” will be reported. The configuration method is spring.data mongodb.uri=mongodb://host1:port1,host2:port2,host3:port3/Database name? replicaSet = replicaset name

    This is because transactions must be based on replica sets to achieve

  • If there is a catch exception, you must add a line of code, otherwise you can still insert into the database when an exception occurs; you can only roll back the insert operation after adding it

    1
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly
  • No need to introduce the spring-data-mongodb package, just spring-boot-starter-data-mongodb, spring-data-commons and mongo-java-driver.

  • @Transactional can only be applied to public methods

@Transactional configuration item

Property NameDescription
nameWhen there are multiple TransactionManagers in the configuration file, you can use this property to specify which transaction manager to choose.
propagationThe propagation behavior of transactions, the default value is REQUIRED.
IsolationThe isolation of the transaction, the default value is DEFAULT.
TimeoutThe timeout time for the transaction, the default value is -1. If the time limit is exceeded but the transaction has not yet completed, the transaction is automatically rolled back.
Read-onlySpecifies whether the transaction is read-only, the default value is false; in order to ignore methods that do not require transactions, such as reading data, you can set read-only to true.
rollback-foris used to specify the exception type that can trigger transaction rollback. If there are multiple exception types to be specified, the types can be separated by commas.
Throws the exception type specified by no-rollback-for, and does not roll back the transaction.

In addition, the @Transactional annotation can also be added at the class level. When the @Transactional annotation is placed at the class level, it means that all public methods of the class are configured with the same transaction attribute information.