MongoDB-事务

mongoDB 执行事务,需要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
# mongod.conf

# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/

# Where and how to store data.
storage:
dbPath: /data/db
journal:
enabled: true
# engine:
# mmapv1:
# wiredTiger:

# where to write logging data.
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log

# network interfaces
net:
port: 27017
# bindIp: 127.0.0.1
bindIpAll: true

# how the process runs
processManagement:
timeZoneInfo: /usr/share/zoneinfo

#security:
# authorization: enabled
# keyFile: /etc/mongo/keyFilers0.key

#operationProfiling:

replication:
oplogSizeMB: 150
replSetName: itdeve-mongodb
sharding:
clusterRole: shardsvr

## Enterprise-Only Options:

#auditLog:

#snmp:
  • 执行docker命令
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
  • 进去容器:docker exec -it mongo /bin/bash

  • 打开mongo命令行 mongo

  • 依次执行命令

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;

/**
* @author cmyang
* @date 2022/11/20 0:57
*/
@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) {

}