spring事务@Transactional失效问题排查。
问题现象
- 简述下问题
- 实际业务编码中遇到的问题,代码敏感,替换为类似结构的栗子
- 通过工厂类AnimalFactory获取动物实例,调用Animal.action()方法,该方法中存在对数据库的CURD
- 针对方法action()打上Transactional注解,然而在抛出异常时却没有事务回滚作用
1 | public abstract class Animal implements InitializingBean{ |
1 |
|
1 |
|
1 |
|
问题环境
- 事务配置
1 | import org.aspectj.lang.annotation.Aspect; |
action命令未AOP事务的规则,故次配置不会在action()中生效,正因如此才加上了
- Spring默认propagation属性为Propagation.REQUIRED
排查思路
- @Transactional失效场景
- @Transactional 应用在非 public 修饰的方法上
- @Transactional 注解属性 propagation 设置错误
- @Transactional 注解属性 rollbackFor 设置错误
- 同一个类中方法调用,导致 @Transactional 失效
- 异常被 catch“吃了”导致 @Transactional 失效
- 数据库引擎不支持事务
- 然而网上常见的几种@Transactional失效场景均无法匹配当前问题场景
即使把问题函数名
aciton
改为addaction
也仍然没有事务回滚通过在代码中加入工具类
TransactionTestUtils.transactionRequired
,直观看出当前函数内是否存在事务
1 | TransactionTestUtils.transactionRequired("action"); |
1 | import java.lang.reflect.InvocationTargetException; |
- 通过观察发现,问题函数内的事务确实没有生效
- 仔细思考,@Transactional事务,依赖SpringAOP实现,那么不如在AOP事务源码处加上断点,看看到底进入什么逻辑导致事物没生效
- 结果发现,连AOP外层逻辑都没进去
- 再想想,AOP实际通过动态代理实现,而动态代理实际通过spring容器实现,也就是函数对应的类实例可能未被容器所管理
- 想到这里,问题点很容器就找到了,AnimalFactory工厂中register()加注册逻辑反转给具体的工厂中的类,而Dog实际通过put的方式将自己存入了
cachedHandlers
中,脱离了容器的管控 - 对于上面一点,有必要解释下,spring实现动态代理是通过代理类+被代理类 两个实例的方式实现的,而不是只有一个代理类,故注册进工厂的是原始Dog实例,而不是被代理类
解决方案
- 既然没有通过spring容器注册实例导致了当前问题,那么咱通过spring容器拿就是了
- register不注册实例,改为注册beanName
1 | "Dog") ( |
- 工厂类load时通过容器获取对应的实例
1 |
|
总结
- 问题在复盘时,顺着捋总是会显得
easy and stupid
,希望能通过不断的总结优化既有的问题思考方法论,以至于以后能少走点弯路