root@119ce58d3c1f:/# mongo
MongoDB shell version v4.4.5
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("c278fd96-dbe5-44bc-a608-3a0bb3307b3d") }
MongoDB server version: 4.4.5
Springboot版本:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
@Bean(name = "mongoTransactionManager")
MongoTransactionManager mongoTransactionManager(MongoDatabaseFactory dbFactory){
return new MongoTransactionManager(dbFactory);
}
@Bean(name = "jdbcTransactionManager")
@Primary
JdbcTransactionManager jdbcTransactionManager(DataSource dataSource){
return new JdbcTransactionManager(dataSource);
}
完整配置如下
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.MongoTransactionManager;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.*;
import javax.sql.DataSource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* AOP事务配置
*/
public class TxAdviceConfig {
@Bean(name = "jdbcTransactionManager")
JdbcTransactionManager jdbcTransactionManager(DataSource dataSource){
return new JdbcTransactionManager(dataSource);
}
@Bean
public TransactionInterceptor txAdvice(JdbcTransactionManager transactionManager) {
/*只读事务,不做更新操作*/
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
/*当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务*/
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
requiredTx.setTimeout(5000);
/*其他事务*/
RuleBasedTransactionAttribute otherTx = new RuleBasedTransactionAttribute();
otherTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
Map<String, TransactionAttribute> txMap = new HashMap<>();
txMap.put("select*", readOnlyTx);
txMap.put("delete*", requiredTx);
txMap.put("update*", requiredTx);
txMap.put("insert*", requiredTx);
txMap.put("save*", requiredTx);
txMap.put("add*", requiredTx);
txMap.put("*", otherTx);
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
source.setNameMap(txMap);
TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
return txAdvice;
}
@Bean
public Advisor txAdviceAdvisor(TransactionInterceptor txAdvice) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.yealink.service..*.*(..))");
return new DefaultPointcutAdvisor(pointcut, txAdvice);
}
/**
* 启用MongoDb事务配置
* @param dbFactory
* @return
*/
@Bean(name = "mongoTransactionManager")
MongoTransactionManager mongoTransactionManager(MongoDatabaseFactory dbFactory){
return new MongoTransactionManager(dbFactory);
}
}
使用这种方式,就可以区分Mysql和MongoDb的事务了。Mysql的事务还是不变,使用声明式;MongoDb的事务使用注解式,此处要注意,使用时要加上对应的名称
@Transactional(value = "mongoTransactionManager")
@Override
@Transactional(value = "mongoTransactionManager")
public void updateArchive(UpdateArchiveReq req, Long optId) {
archiveDao.save(req.getArchive());
int i = 1/0;
List<Document> documentList = req.getDocumentList();
if(CollectionUtils.isNotEmpty(req.getDocumentList())){
documentDao.saveAll(documentList);
}
}
执行以上逻辑时,抛出异常,数据不会保存,会进行回滚。
这里有个大坑需要注意,MongoDb的事务只在集群模式下才支持,如果在单机模式下,执行保存动作时,会抛出以下异常
This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string.
所以我又在Docker上把MongoDb调整成了伪集群模式,具体配置方式见另一篇文章:Docker部署MongoDb伪集群
另外,由于Spring一个方法不支持多个Transactional,因此不支持同时两个数据库的事务,如果在某个事务中同时操作2个数据库,事务只对当前数据库事务生效,另个不生效,如果想要实现多数据库事务,可以通过自定义注解的方式实现,参考Spring Boot多数据源事务管理