前言
前几天在做一个项目的时候遇到的一个问题,在事务中动态切换数据源报错,于是上网百度了一下@Transaction注解的执行逻辑,然后才恍然大悟....
调用链
事务代码调用链:
service注解上@transactional-->TransactionInterceptor.interpter()-->TransactionAspectSupport.createTransactionIfNecessary()-->AbstractPlatformTransactionManager.getTransaction()-->DataSourceTransactionManager.doBegin()-->AbstractRoutingDataSource.determineTargetDataSource()[lookupKey==null去拿默认的Datasource, 不为空则使用获取到的连接]
-->DataSourceTransactionManager.setTransactional()[将连接设置到TransactionUtils的threadLocal中]
--->Repository@Annotation-->执行一般调用链, 问题在于SpringManagedTransaction.getConnection()
-->openConnection()
-->DataSourceUtils.getConnection()
-->TransactionSynchronizationManager.getResource(dataSource)不为空[从TransactionUtils的threadLocal中获取数据源], 所以不会再去调用DynamicDataSource去获取数据源
问题分析与解决
从这里可以看出来,每次都会从threadLocal中去拿数据源,如果为空,就创建新的连接,如果不为空的话直接复用,而threadLocal是线程型的变量,只要在同一个中,就只会存在一个,所以这就会导致你切换数据源失败,因为threadLocal不为空。
解决方案
你可以把threadLocal变量给remove掉,不过threadLocal是private是私有的,可以根据反射修改,网上有一套解决方案,有点麻烦。
因为我的场景是在一个事务中mysql写,而sqlserver读,所以sqlserver实质上不需要事务,所以我新开了一个线程来获取sqlserver的值,搭配futureTask来获取异步的返回值(get阻塞),这样就可以实现了(因为新开了一个线程之后threadLocal就是null了)
这里还会存在一个问题,假设你使用的是线程连接池的话,还要考虑一下工作线程复用的情况(这样也会导致threadLocal不为空)
最好的办法应该就是分布式事务的处理,不过我还不太了解...