spring-boot-mongodb-transaction

今天在工作中需要使用到mongodb的tansaction,于是查阅了各种资料,也踩了不少的坑,在这里总结一下。

前提

  • MongoDB的版本必须是4.0版本以上
  • 引入spring-data

配置

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);
}

注意事项

  • 注解必须是@Transactional(rollbackFor = { Exception.class }),不能仅仅是@Transactional

  • 必须在properties文件中配置replica-set,否则报错"Sessions are not supported by the MongoDB cluster to which this client is connected"。配置方式是spring.data.mongodb.uri=mongodb://host1:port1,host2:port2,host3:port3/数据库名称?replicaSet=复制集名称

    这一点是因为事务必须基于副本集才能实现

  • 如果有捕获异常,则必须加一行代码,否则发生异常时还是能insert到数据库;加了后才能回滚insert操作

    1
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly
  • 不必引入spring-data-mongodb包,只需要spring-boot-starter-data-mongodb、spring-data-commons和mongo-java-driver即可。

  • @Transactional 只能应用到 public 方法才有效

@Transactional配置项

属性名说明
name当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
propagation事务的传播行为,默认值为 REQUIRED。
isolation事务的隔离度,默认值采用 DEFAULT。
timeout事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
read-only指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollback-for用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for抛出 no-rollback-for 指定的异常类型,不回滚事务。

除此以外,@Transactional 注解也可以添加到类级别上。当把@Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。