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
解析我们的配置文件开始看起,我们从Spring
在Web.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
类中