springboot设置双数据源,事务@Transitional注解的一些问题

本文探讨了在SpringBoot项目中遇到的多数据源事务管理问题。当使用@Transational注解时,由于配置了多个数据源,导致出现找不到符合条件的PlatformTransactionManager错误。解决方案是通过在@Transactional注解中指定事务管理器,如`transactionManager=mysqlTransactionManager`,以确保特定数据源的事务。此外,提出了一种通过在两个方法中分别指定不同数据源的事务管理器,以实现跨数据源事务一致性的方法。

记一个最近遇到的问题,等后面了解springboot中事务管理的源码之后会来更新详细说明

· 关于springboot中多数据源的配置,查看之前的文章

项目场景:

由于最近做的项目中遇到多数据源的业务场景,就在思考一个问题,如果一个操作里面包含了多个数据库操作,并且这些数据库操作的数据源不止一个。需要使用事务来,保持数据一致性。


问题描述:

为了能够使得多个数据库操作能保持数据一致,就会使用到事务,但是如果使用springboot的@Transitional注解来做事务会报错

@Override
    @Transactional(rollbackFor = Exception.class)
    public void testData(){
        mysqlDataMapper.updateTest();//操作数据库A
        syncDataDao.updateTest();//操作数据库B
        throw new RuntimeException();
    }

错误信息:
No qualifying bean of type ‘org.springframework.transaction.PlatformTransactionManager’ available: more than one ‘primary’ bean found among candidates: [mysqlTransactionManager, oracleTransactionManager]


原因分析:

从提示信息可以看出,原因是因为配置了多个数据源,存在多个事务管理器PlatformTransactionManager

解决方案:

可以在@Transitional中添加事务属性,指定事务管理器:
transactionManager = “mysqlTransactionManager”
但是这样只能对指定的数据源启动事务。其他有待研究方案
在这里插入图片描述
下面是最近想到的一个解决方案:

	@Transactional(rollbackFor = Exception.class ,transactionManager = "aTransactionManager")
    public void testA() throws Exception {

        /**
         * 执行A数据源的数据库操作
         */
        //---其他业务逻辑

        testB();//---把B数据源的数据库操作放在最后
    }
    
	@Transactional(rollbackFor = Exception.class ,transactionManager = "bTransactionManager")
    public void testB() throws Exception {

        /**
         * 执行B数据源的数据库操作
         */
        throw new Exception("ddd");
        // 当B操作失败时,会回滚数据库操作。报出异常
    }

按照上面代码逻辑去写就可以保证双数据源的时候事务一致性。分析如下:

  • 第一种情况当操作A数据源的时候发生错误,错误会被testA上面的Transactional注解捕获,回滚A数据源的操作。根据java的执行顺序,当前面逻辑报错之后就不会执行后面的代码逻辑了。所以,testB是数据操作不会被执行。
  • 第二种情况就是操作数据源A的时候没有发生错误,在testB中操作数据源B的时候发生了错误,那么错误信息会被testB上面的Transactional注解捕获,从而回滚数据库操作。这时,将异常传递到testA方法内,同样会被testA上的Transactional注解捕获,从而回滚A数据源的数据库操作。达到事务的一致性。
Java中,`@Transactional` 注解与 `getGenericInterfaces()` 方法分别属于不同的技术范畴,前者是Spring框架中用于声明事务管理的注解,后者是Java反射API中用于获取类或接口所实现的泛型接口的方法。二者本质上并不存在直接冲突,但在某些特定场景下可能会产生间接的干扰或影响,尤其是在涉及动态代理、泛型接口实现以及事务边界管理时。 当使用Spring的 `@Transactional` 注解管理事务时,Spring会通过动态代理机制为带有该注解的类或方法生成代理对象。这种代理机制在处理接口时通常会使用JDK动态代理,而JDK动态代理依赖于接口的反射信息,包括通过 `getGenericInterfaces()` 获取的泛型接口定义。如果在接口中使用了复杂的泛型结构,例如嵌套泛型或通配符泛型,那么在代理对象创建过程中可能会因类型解析问题导致代理失败或事务管理失效[^1]。 例如,当接口中存在泛型定义如下: ```java public interface Service<T> { void process(T data); } ``` 而实现类如下: ```java @Transactional public class StringService implements Service<String> { @Override public void process(String data) { // 业务逻辑 } } ``` 此时,Spring在创建代理对象时会通过反射获取 `StringService` 的接口信息,包括其泛型父接口 `Service<String>`。若泛型结构复杂或存在类型擦除后的类型信息丢失问题,可能会导致代理对象无法正确识别事务边界,从而影响事务的一致性控制[^1]。 为了解决此类问题,可以采取以下措施: 1. **确保泛型接口的结构清晰且易于解析**。避免使用过于复杂的嵌套泛型或通配符泛型,以减少类型解析的难度。 2. **显式指定事务管理的接口类型**。在配置事务管理器时,可以通过指定事务接口的具体类型来避免因泛型推导失败导致的事务失效。 3. **使用CGLIB代理代替JDK动态代理**。对于没有接口定义的类,Spring默认使用CGLIB代理;对于有接口的类,可以通过配置强制使用CGLIB代理,从而避免因泛型接口解析问题导致的代理失败[^1]。 ```java @EnableTransactionManagement(proxyTargetClass = true) ``` 4. **对泛型接口进行适配封装**。在必要时,可以将复杂的泛型接口封装为非泛型接口,或者通过适配器模式将泛型逻辑与事务逻辑分离,从而降低两者的耦合度。 通过上述方式,可以有效缓解因 `@Transactional` 注解与 `getGenericInterfaces()` 方法在泛型接口处理上的潜在冲突,确保事务管理的正确性和稳定性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值