Spring中事务@Transactional失效场景(6大种)
一、@Transactional注解方法为非public
@Service
public class TestServiceImpl implements TestService {
@Resource
TestMapper mapper;
@Transactional
void insertTest() {
mapper.insert(test1);
mapper.insert(test2);
}
}
原因:@Transactional是基于动态代理实现的,在bean初始化过程中,Spring扫描@Transactional注解信息,对含有@Transactional标注的bean实例创建代理对象。
Spring加载时拦截bean的创建,创建一个aop切点BeanFactoryTransactionAttributeSourceAdvisor,遍历当前bean的class对象方法,判断方法上面的注解信息是否包含@Transactional,如果bean任何一个方法包含@Transactional注解,那么就是适配这个切点,就需要创建代理对象,然后代理逻辑为我们管理事务开闭逻辑。
类所有方法上的修饰符都是非public时,将不会创建代理对象。public修饰符的类经过cglib创建代理对象。
类中多个方法,其中一个为public修饰方法,将会创建代理对象,但非public修饰的方法仍然不会开启事务。原因是在动态代理对象进行代理逻辑调用时,会获取当前被代理对象的需要执行的method适配的aop逻辑,非public方法不适配BeanFactoryTransactionAttributeSourceAdvisor,就不执行代理@Transactional对应的代理逻辑,直接执行方法,就无法开启事务。
AopUtils#canApply(org.springframework.aop.Pointcut, java.lang.Class<?>, boolean)
AbstractFallbackTransactionAttributeSource#getTransactionAttribute
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
二、类内部调用当前类内部@Transactional标注的方法
@Service
public class TestServiceImpl implements TestService {
@Resource
TestMapper mapper;
@Transactional
public void insertTest() {
mapper.insert(test1);
mapper.insert(test2);
}
public void test(){
insertTest(); //类内部调用@Transactional标注的方法
}
}
原因:事务管理是基于动态代理对象的代理逻辑实现的,那么如果在类内部调用类内部的事务方法,这个调用事务方法的过程并不是通过代理对象来调用的,而是直接通过this对象来调用方法,绕过代理对象,肯定就是没有代理逻辑了。
解决:在类的内部注入当前类自己,通过注入的类对象调用类内其他@Transactional方法,这样就是使用了代理对象进行事务调用,所以能够开启事务管理。实际操作中不会这样去做。
三、@Transactional标注的方法内部捕获了异常
@Service
public class TestServiceImpl implements TestService {
@Resource
TestMapper mapper;
@Transactional
public void insertTest() {
try {
mapper.insert(test1);
mapper.insert(test2);
} catch (Exception e) {
System.out.println(e);
}
}
}
原因:代理逻辑管理事务,是在开启事务后,通过反射调用业务方法,发生异常时try-catch捕获,在catch逻辑中回滚事务。如果业务方法内部捕获了异常,代理逻辑中就捕获不到异常,事务就不会回滚了。
TransactionAspectSupport#invokeWithinTransaction
针对这种情况,在catch中添加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
@Service
public class TestServiceImpl implements TestService {
@Resource
TestMapper mapper;
@Transactional
public void insertTest() {
try {
mapper.insert(test1);
mapper.insert(test2);
} catch (Exception e) {
System.out.println(e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}