后端MongoDBMongoDB-事务
cmyangmongoDB 执行事务,需要mongodb的集群模式,需要搭建一个mongo集群
1. docker搭建伪集群
如果需要支持mongo的事务,需要集群模式,这里搭建一个简单的伪集群做事务测试
- 编写配置文件
vim /dockerData/mongo/config/mongod.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
storage: dbPath: /data/db journal: enabled: true
systemLog: destination: file logAppend: true path: /var/log/mongodb/mongod.log
net: port: 27017
bindIpAll: true
processManagement: timeZoneInfo: /usr/share/zoneinfo
replication: oplogSizeMB: 150 replSetName: itdeve-mongodb sharding: clusterRole: shardsvr
|
1 2 3 4
| docker run -d --name mongo -p 27017:27017 \ -v /dockerData/mongo/data/db:/data/db \ -v /dockerData/mongo/config/mongod.conf:/etc/mongo/mongod.conf \ mongo:4.4.5 --config /etc/mongo/mongod.conf
|
1 2 3 4
| use admin rs.initiate() rs.conf() exit
|
2. 添加事务支持
1 2 3 4 5 6 7 8
| @Configuration public class MongoConfig {
@Bean public MongoTransactionManager mongoTransactionManager(MongoDatabaseFactory mongoDatabaseFactory) { return new MongoTransactionManager(mongoDatabaseFactory); } }
|
- 接下来只需要再service方法上添加
@Transactional
注解即可
3. mysql与mongo事务冲突
如果项目中同时使用了mysql和mongo,并且都使用的事务,会导致mysql的事务失效,无法回滚。因为都实现了PlatformTransactionManager,springboot容器只会加载一个,所需如果需要同时加载两个PlatformTransactionManager的bean。需要对bean从命名,并且保证mysql事务方式不变,指定mysql事务为主
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package cn.aacopy.learn.mongo.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.MongoTransactionManager; import org.springframework.jdbc.support.JdbcTransactionManager;
import javax.sql.DataSource;
@Configuration public class MongoConfig {
@Bean("mongoTransactionManager") public MongoTransactionManager mongoTransactionManager(MongoDatabaseFactory mongoDatabaseFactory) { return new MongoTransactionManager(mongoDatabaseFactory); }
@Bean("jdbcTransactionManager") @Primary public JdbcTransactionManager jdbcTransactionManager(DataSource dataSource) { return new JdbcTransactionManager(dataSource); } }
|
- mysql的事务还是保持原来的使用方式
- mongodb在添加
@Transactional
注解时,需要指定PlatformTransactionManager 的具体bean名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Service public class TestAServiceImpl implements TestAService {
@Autowired private TestARepository testARepository;
@Transactional("mongoTransactionManager") public TestADO saveTestA() { TestADO testADO = new TestADO(); testADO.setName("嘎嘎"); testARepository.save(testADO); int i = 1/0; return testADO; } }
|
- 这样就解决了同时支持mysql和mongo事务的问题
4. 并发报错WriteConflict
mongo本身不支持事务,直接使用事务注解在并发情况下会报错WriteConflict。
官方提供的方案是,通过重试的方式spring-retry,但这种方式,很影响效率,
1 2 3 4
| <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
|
- 启动类上添加
@EnableRetry
- 编写自定义mongo事务注解@MongoTransactional
1 2 3 4 5 6 7 8
| @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Retryable(value = UncategorizedMongoDbException.class, exceptionExpression = "#{message.contains('WriteConflict error')}", maxAttempts = 128, backoff = @Backoff(delay = 500)) @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class, timeout = 120) public @interface MongoTransactional {
}
|
1 2 3 4
| @MongoTransactional public save(User user) {
}
|