Spring工程多数据源多个事务管理器导致事务失效

3,561 阅读4分钟

1.背景

最新公司在按产品线拆分数据库做Mysql多活,导致一个工程中有多个数据源,我们产品线是最先拆出来的所以这些数据源,事务管理器的配置都是默认的,正常使用,但是在其他产品线加入新的数据源及事务管理器后发现事务失效了,怀疑和配置方式有关。

2.原因分析

话不多说先上代码为敬,至于为啥打码,你懂的,com后边一般来说是公司名称了呀。

上图的1是后来的,后来者居上嘛,pxhTransactionManager是没有问题的,因为好名字transactionManager已经被我们占了嘛(首发除了探坑还是有好处的),问题应该就出在了又定义了一次<tx:annotation-driven>标签。

这里边挺奇怪的,一般大家在工程中就直接是<tx:annotation-driven/>声明下这个注解就行了,为啥还要写transaction-manager属性呢,这里我们推测(其实就是这样)这是指定事务的默认的管理器,既然是默认那就有个默认的名字,没错如大家所想默认的名字就是transactionManager,其实IDEA已经给我们提示了,如下图

无用的声明默认属性,而且鼠标放上去Alt+Enter第一个提示是可以删除的

定义多次tx标签就是很奇怪的事情,这可以在单例天下的Spring中,而且我们根据结果和代码可以猜测,<tx:annotation-driven>标签采取的是先入为主的策略,导致我们默认调用的事务管理器变成了pxhTransactionManager,然而这并不是我们代码需要调用的数据源,所以回滚无效

3.代码分析

猜测必须要以代码为依据,既然标签是定义在XML文件中的,那么我们就从Spring解析我们的配置文件开始看起,我们从SpringWeb.xml文件中定义的启动窗口开始,就是大家都知道的ContextLoaderListener

Listener 均是ServletContextListener的实现类在容器启动的时候自行执行contextInitialized方法,进入contextLoader.initWebApplicationContext方法

这个方法主要是初始化容器上下文,具体的bean加载等方法在configureAndRefreshWebApplicationContext()方法中

进入wac.refresh()方法,这里的wac是类是XmlWebApplicationContext,上边的就不说了,太烦了没看懂……

XmlWebApplicationContext 这个类的继承很复杂,这里的refresh() 实际的代码在AbstractApplicationContext类中

直奔主题进入obtainFreshBeanFactory()方法

loadBeanDefinitions(beanFactory)方法解析XML文件,进入AbstractXmlApplicationContext类中的loadBeanDefinitions()方法中

进入XmlWebApplicationContext.loadBeanDefinitions()方法

进入AbstractBeanDefinitionReader.loadBeanDefinitions()的一系列重载方法,然后进入AbstractBeanDefinitionReader类的loadBeanDefinitions(String location, Set<Resource> actualResources)方法

进入XmlBeanDefinitionReader类的loadBeanDefinitions(EncodedResource encodedResource)方法

经过XmlBeanDefinitionReader类的doLoadBeanDefinitions()方法进入registerBeanDefinitions()方法,

进入DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法

然后进入parseBeanDefinitions()方法

tx:这样的标签属于自定义标签走BeanDefinitionParserDelegate.parseCustomElement()方法

重点在于通过命名空间找到对应的处理类,进入DefaultNamespaceHandlerResolver类的resolve()方法

下边是handlerMappings命名空间和处理类的对应关系

进入TxNamespaceHandler类,可以看出来初始化了几个关键的类

回到刚才的入口,进入NamespaceHandlerSupport.parse()方法

可以看出来需要执行AnnotationDrivenBeanDefinitionParser.parse()方法

可以发现我们熟悉的包都有身影出现,这里我们猜也是工厂模式的一种了,这里其实就是各种需要引入命名空间的标签的处理类了。我们需要进入的是 spring-tx包,在AnnotationDrivenBeanDefinitionParser的内部静态类AopAutoProxyConfigurer的静态方法configureAutoProxyCreator是关键,这边也解释了为什么先入为主,

只有当不存在这个类型的bean的时候,才会对bean初始化并且注册到容器中,而xml解析是从上到下的,所以我们被放在下边的事务管理器并不会起到应有的作用,而是被无情的抛弃。

同样在这部分的代码解释了transaction-manager标签属性的解析过程,默认值也是在此设置的

TransactionInterceptor类的invoke()方法是事务代理对象的具体执行者,具体的代码在TransactionAspectSupport.invokeWithinTransaction()类,

上图的completeTransactionAfterThrowing方法,决定了在什么异常发生时回滚,如果@Transactional注解没有指定rollbackFor属性的话,就进入DefaultTransactionAttribute,s所以只会在RuntimeException类型异常和Error时回滚

4.解决方案

其实解决方案网上都有,就是在@Transactional注解里边指定对应的事务管理器,例如 @Transactional("pxhTransactionManager") @Transactional("transactionManager")

具体原因看下边的代码就知道了在TransactionAspectSupport类中