26

Spring 下,关于动态数据源的事务问题的探讨

 4 years ago
source link: http://www.cnblogs.com/youzhibing/p/12671004.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

开心一刻

毒蛇和蟒蛇在讨论谁的捕猎方式最高效。

毒蛇:我只需要咬对方一口,一段时间内它就会逐渐丧失行动能力,最后死亡。

蟒蛇冷笑:那还得等生效时间,我只需要缠住对方,就能立刻致它于死地。

毒蛇大怒:你缠它身子,你下贱!

蟒蛇:你不也亲了它吗?

ji6bay3.gif

前情回顾

看着文章的标题,不知道大家能否想到具体是什么问题,如果你有点懵,那就对了! (你不懵的话我这篇文章就没存在的意义了,嘿嘿)

在给大家指出具体是什么问题时,我们先来回顾一些内容

Spring 事务原理

相信大家对这个都能说上来一些,Spring 事务是 Spring AOP 的一种具体应用,底层依赖的是动态代理

大致流程类似如下

iaQFbi7.png!web

通过代理对象来调用目标对象,而在代理对象中有事务相关的增强处理

具体细节可参考以下文章

Spring 事务源码解析

结合 ThreadLocal 来看 Spring 事务源码,感受下清泉般的洗涤!

记一次线上问题 → 事务去哪了

Spring 动态数据源原理

原理解密 → Spring AOP 实现动态数据源(读写分离),底层原理是什么 中已经详细介绍过了,流程大体如下

3y6Rfq2.png!web

Spring AOP → 将我们指定的 lookupKey 放入 ThreadLocal

ThreadLocal → 线程内共享 lookupKey

DynamicDataSource → 对多数据源进行封装,根据 ThreadLocal 中的 lookupKey 动态选择具体的数据源

有什么问题

既然事务和动态数据源都是 Spring AOP 的具体应用,那么代理就存在先后顺序了

要么是

67rIvii.png!web

要么是

JFVveij.png!web

我们来看看这两者有什么区别

事务在前,动态数据源在后

此时,事务的前置增强处理会先生效,那么此时开始事务获取的 Connection 从哪来 ? 肯定是从 DynamicDataSource 来,因为我们给事务管理器配置的就是它

    @Bean
    public PlatformTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) {
        // 配置事务管理, 使用事务时在方法头部添加@Transactional注解即可
        return new DataSourceTransactionManager(dynamicDataSource);
    }

既然是从 DynamicDataSource 获取的 Connection,那 DynamicDataSource 根据 lookupKey 获取 Connection 的时候,会从 masterDataSource 数据源获取还是从 slaveDataSource 数据源获取 ?因为此时还未将 lookupKey 绑定到当前线程,那么 DynamicDataSource 会从默认数据源获取,而我们配置的默认数据源是 slaveDataSource

    /**
     * 获取当前线程的数据源
     * @return
     */
    public static DataSourceType getDataSourceType()
    {
        return  HOLDER.get() == null ? DataSourceType.SLAVE : HOLDER.get();
    }

说白了,此时的动态数据源对事务不生效,事务始终从默认数据源获取 Connection,而没有动态的效果,这就是问题了

Talk is cheap. Show me the code,我们来看看是不是真的如上所说

qay2QvU.gif

192.168.0.112 正是我们的从库,对应的就是我们的默认数据源 slaveDataSource 

动态数据源在前,事务在后

此时,动态数据源的前置增强会先执行,DynamicDataSource 需要的 lookupKey 会先于事务绑定到当前线程,那么事务从 DynamicDataSource 获取 Connection 的时候就能根据当前线程的 lookupKey 来动态选择 masterDataSource 还是 slaveDataSource

此种情况是没有问题的

解决问题

总结下问题:如何保证事务中的动态数据源也有动态的效果,也就是如何保证动态数据源的前置增强先于事务

我们知道 Spring AOP 是能够指定顺序的,只要我们显示的指定动态数据源的 AOP 先于 事务的 AOP 即可;如何指定顺序,常用的方式是实现 Order 接口,或者使用 @Order 注解,Order 的值越小,越先执行,所以我们只需要保证动态数据源的 Order 值小于事务的 Order 值即可

我们先来看看事务的 Order 值默认是多少,在 EnableTransactionManagement 注解中

    /**
     * Indicate the ordering of the execution of the transaction advisor
     * when multiple advices are applied at a specific joinpoint.
     * <p>The default is {@link Ordered#LOWEST_PRECEDENCE}.
     */
    int order() default Ordered.LOWEST_PRECEDENCE;

默认是最低级别(值非常大),那么我们只需要保证动态数据源的 Order 比这个值小就好,我们就取 1

  @Component
  @Slf4j
  @Order(1)
  public class DynamicDataSourceAspect {

我们在来看看是否真的可行

aYJfymf.gif

已经不是默认的 slaveDataSource ,而是我们指定的 masterDataSource(通过 @MasterSlave(MASTER) 指定)

至此,相信大家已经弄清楚了有什么问题,以及如何解决它

什么,还没理解 ? 你过来,我保证不打死你

qI7JNrE.gif

总结

1、不只是动态数据源和事务,只要涉及到多个 AOP,就可能会有顺序问题,这是值得大家注意的

2、相关约束

主数据库执行 INSERT UPDATE DELETE 操作,可能还有部分 SELECT 操作(主从同步多少有延时)

从数据库只执行 SELECT 操作

默认数据源最好设置成主数据源,防止粗心将更新操作执行到了从数据库;楼主之所以设置成从数据源,是考虑到绝大多数数据库操作是查询,这样可以减少代码量;具体怎么选,需要大家结合实际情况来决定


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK