1、Transactional注解原理:
首先Transactional注解基于动态代理实现,在代理类中,会增强有该注解的方法,增强的地方有:
(1)在执行方法前将事务交由Spring事务管理器托管,并且将事务的autoCommit属性置为false(即:禁止事务的自动提交);
(2)在执行方法的时候检查是否有异常抛出(默认是RuntimeException),如果有,则事务 rollback-回滚;
(3)在执行方法后,如果一直没有异常抛出,则判定执行成功,然后执行 commit-事务提交。
通过以上三个方面,实现了事务。
2、Transactional注解失效情形及分析:
Transactional注解可能会失效,类似情况如下:
@Transactional
public void m1() {
// ...执行m1相关
this.m2();
}
@Transactional
public void m2() {
// ...执行m2相关
}
如代码所示,在同一个bean中,m1方法直接调用m2方法,m2方法上标注的Transactional注解会失效。
分析原因:
假设现在有一个bean:
@Component
public class MyBean {
@Transactional
public void m1() {
// 执行m1相关...
this.m2();
}
@Transactional
public void m2() {
// 执行m2相关...
}
}
此时,生成动态代理后,代理对象结构类似如下(仅列出大概结构):
public class MyBeanProxy extends MyBean {
private MyBean target;
// 动态代理生成的增强方法
public void m1() {
// ...在这里关闭事务自动提交
target.m1(); // 这一步里面会去调用target.m2()
// ...在这里提交事务
}
}
这里坑点在于,动态代理类的结构中,是将原MyBean对象作为成员放在代理类对象中,即target成员。
当我们在外面通过Spring拿到Bean对象之后,调用m1方法,其实是通过代理对象访问了m1方法,这个方法中增强了原对象的m1方法,即前后包裹了事务处理,但真正执行业务逻辑时,使用的还是代理对象中包含的原对象成员,即上面代码中的target成员。
因此m1方法中调用m2时,其实是target对象去调用m2方法了,而target对象并不是代理对象,Spring就不会对m2包裹上事务的处理,m2上的Transactional在这种调用的情况下,就失效了。
当然,这里是说m2上的事务在这里失效了,但是m1的事务仍然有效的,m2在m1中被调用,仍然是被包裹在m1上标识的事务之内的,示例代码中m1和m2都使用了默认的事务传播机制PROPAGATION_REQUIRED ,这种传播机制不会相互冲突,因此不会有太大问题,但如果m1和m2上事务的传播机制不同或者有冲突的时候,则会出现丢失m2事务注解的效果。
解决方案:
禁止在bean内部调用标识了Transactional注解的方法,以Spring中取得的bean对象直接调用该方法。