Springboot MongoDb Mysql 多数据源 事务配置

  1. 软件版本:
    MongoDb:
    使用Docker进行部署
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>
  1. 场景:
    由于项目部分模块更适合使用mongoDb,于是尝试引入MongoDb,原项目本来是Springboot+MybatisPlus+Mysql的架构。
    由于MongoDb在现有版本已经支持事务
    于是想进行事务配置,实现Mysql和MongoDb混合事务
  2. 事务启用配置:
    由于项目本身还有用到Mysql,用的是AOP声明式事务配置,此次添加MongoDb只要用注解式事务即可。
    在配置时要注意,此时会存在两个TransactionManager,一个是jdbcTransactionManager还有一个是
    MongoTransactionManager。
    为了不冲突,需要配置各自的名称。(如果没有多数据源,则无需此步)
	@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")
  1. 验证
    @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多数据源事务管理

你可能感兴趣的:(mongodb,spring)