好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

@Transactional跟@DS动态数据源注解冲突的解决

@Transactional跟@DS动态数据源注解冲突

背景

前阵子写一个项目时,有个需求是要往3个库,3个表里插入数据,在同一个方法里,公司是用baomidou的@DS注解来实现配置动态数据源的。这是背景,然后呢,我在一个service方法里,就操作了这三张表,同时,我还加上了@Transactional注解,因为该方法是个save方法,我就加上了事务。

伪代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

spring:

   datasource:

     dynamic:

       primary: A

       datasource:

         A:

           url:..

           driver- class -name: com.mysql.jdbc.Driver

           username: root

           password:

         B:

           url: ..

           driver- class -name: com.mysql.jdbc.Driver

           username: root

           password:

         C:

           url: ..

           driver- class -name: com.mysql.jdbc.Driver

           username: root

           password:

?

1

2

3

4

5

6

@Mapper

@DS ( "A" )

public interface AMapper{

       @Insert ( "insert into a ..." )

       void save();

}

?

1

2

3

4

5

6

@Mapper

@DS ( "B" )

public interface BMapper{

       @Insert ( "insert into b ..." )

       void save();

}

?

1

2

3

4

5

6

@Mapper

@DS ( "C" )

public interface CMapper{

       @Insert ( "insert into c ..." )

       void save();

}

?

1

2

3

4

5

6

7

8

9

10

11

public class aService{

@Autowired

private aMapper、bMapper、cMapper

 

     @Transactional

     public void save(){

     aMapper.save();

     bMapper.save(); #报错

     cMapper.save();

     }   

}

在开发完成用postman自测时,发现bMapper.save()那一行报错了,报错内容:找不到b表。如果把@Transactional注释掉,代码正常运行,数据成功落库。我们明明在3个mapper上面都加了@DS注解来切换数据源,那为啥加了@Transactional就不行了呢?

@Transactional执行流程

save方法添加了 @Transactional 注解,Spring 事务就会生效。此时,Spring TransactionInterceptor 会通过 AOP 拦截该方法,创建事务。而创建事务,势必就会获得数据源。那么,TransactionInterceptor 会使用 Spring DataSourceTransactionManager 创建事务,并将事务信息通过 ThreadLocal 绑定在当前线程。   而事务信息,就包括事务对应的 Connection 连接。那也就意味着,还没走到 OrderMapper 的查询操作,Connection 就已经被创建出来了。并且,因为事务信息会和当前线程绑定在一起,在 OrderMapper 在查询操作需要获得 Connection 时,就直接拿到当前线程绑定的 Connection ,而不是 OrderMapper 添加 @DS 注解所对应的 DataSource 所对应的 Connection 。 OK ,那么我们现在可以把问题聚焦到 DataSourceTransactionManager 是怎么获取 DataSource 从而获得 Connection 的了。对于每个 DataSourceTransactionManager 数据库事务管理器,创建时都会传入其需要管理的 DataSource 数据源。在使用 dynamic-datasource-spring-boot-starter 时,它创建了一个 DynamicRoutingDataSource ,传入到 DataSourceTransactionManager 中。 而 DynamicRoutingDataSource 负责管理我们配置的多个数据源。例如说,本示例中就管理了 a、b、c 三个数据源,并且默认使用 a 数据源。那么在当前场景下,DynamicRoutingDataSource 需要基于 @DS 获得数据源名,从而获得对应的 DataSource ,结果因为我们在 Service 方法上,并没有添加 @DS 注解,所以它只好返回默认数据源,也就是 a 。故此,就发生了 找不到表 的异常。

我们在上面了解到,因为@Transactional会创建事务然后获得数据源,因为我们service方法上没有@DS注解,就拿了默认数据源,并且在这之后,这个事务信息会通过threadLocal跟当前线程绑定,事务信息包括了connection连接,也就意味着,在进入这个service方法的时候,当前事务就绑定了数据源a,在运行到bMapper.save()时,因为connection已经存在,所以拿到的数据源还是a,这时候就找不到b库里的表了。

解决方法

把这三个入库操作分为3个独立的方法,并且都加上@Transactional和 @DS注解(在service上加)。ps:在完成了aMapper.save()之后去调用bMapper.save()时,一定要把@Transactional设置为Propagation.REQUIRES_NEW,这样在调用另一个事务方法时,TransactionInterceptor 会将原事务挂起,暂时性的将原事务信息和当前线程解绑。

pps:

在一个事务方法里用this来调用另一个事务方法时,@DS也会起作用,原因是this调用的不是事务对象,不会开启事务。想具体了解可以看我之前发的这篇文章 http://HdhCmsTesttuohang.net/article/214498.html

动态数据源切换失败

由事务@Transactional注解导致动态数据源切换失效的问题

不多BB,直接上代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

public class DataSourceKey {

     /**

      * 用户数据源

      */

     public final static String USER = "userDataSource" ;

     /**

      * 报表数据源

      */

     public final static String REPORT = "reportDataSource" ;

     /**

      * 所有数据源的集合

      */

     final static List<String> SOURCES = ImmutableList.of(USER, REPORT);

     /**

      * 根据包名找到数据源, 多数据源的前缀不能存在相同的。 例: user -> userDataSource

      *

      * @param pack 包名

      * @return 数据源名

      */

     public static String getDataSourceKey(String pack) {

         return SOURCES.stream().filter(s -> s.startsWith(pack)).findFirst().orElse(USER);

     }

}

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

@Component

@Aspect

@Order (- 1 )

@Slf4j

public class DynamicDataSourceAspect {

     @Pointcut ( "execution(* com.in.g.data.mapper..*.*(..))" )

     public void dataSourcePointcut() {

     }

     @Before ( "dataSourcePointcut()" )

     public void doBefore(JoinPoint point) throws Throwable {

         log.debug( "切换数据源开始。。。。。。。。。。。。" );

         Package pack = point.getSignature().getDeclaringType().getPackage();

         String str = StringUtils.substringAfterLast(pack.getName(), "." );

         String dataSourceKey = DataSourceKey.getDataSourceKey(str);

         DynamicDataSourceHolder.set(dataSourceKey);

         log.debug( "切换数据源成功,当前数据源:{}" , dataSourceKey);

     }

     @After ( "dataSourcePointcut()" )

     public void doAfterReturning() throws Throwable {

         DynamicDataSourceHolder.clear();

     }

}

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

/**

  * 动态数据源持有者

  */

public class DynamicDataSourceHolder {

     public static ThreadLocal<String> keyHolder = new ThreadLocal<>();

     public static void clear() {

         keyHolder.remove();

     }

     public static void set(String key) {

         keyHolder.set(key);

     }

     public static String get() {

         return keyHolder.get();

     }

}

?

1

2

3

4

5

6

7

8

9

/**

  * 动态数据源配置

  */

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

     @Override

     protected Object determineCurrentLookupKey() {

         return DynamicDataSourceHolder.get();

     }

}

?

1

2

3

4

5

6

7

8

9

10

11

12

13

//正确的代码   --这里偷懒了,直接controller调用dao层

@GetMapping ( "/testdb" )

     public String testDateSources(){

     //缩小事务的范围

        add();

        rptFieldMapper.selectxxxx();

       

        return "sss" ;

     }

     @Transactional (rollbackFor = Exception. class )

     public void add() {

      userMapper.insertXXXX(xxxx);

  }

?

1

2

3

4

5

6

7

8

9

//错误的代码,@Transactional注解会导致 数据源切换失败

@GetMapping ( "/testdb" )

@Transactional (rollbackFor = Exception. class )

public String testDateSources(){ 

     userMapper.insertXXXX(xxxx);

     rptFieldMapper.selectXXXX();

    

     return "sss" ;

}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

原文链接:https://blog.csdn.net/weixin_40378837/article/details/106554198

查看更多关于@Transactional跟@DS动态数据源注解冲突的解决的详细内容...

  阅读:19次