Spring事务为什么会失效?(二)
BeanFactoryTransactionAttributeSourceAdvisor
对应的Advice的实现类为TransactionInterceptor,即针对事务增强的逻辑都在这个类中。
筛选的逻辑我们就先不分析了,后面会再简单提一下
我们来看针对事务增强的逻辑,当执行被@Transactional标记的方法时,会调用到如下方法(TransactionInterceptor#invoke有点类似我们的@Around)
TransactionInterceptor#invoke
TransactionAspectSupport#invokeWithinTransaction
我挑出这个方法比较重要的几个部分来分析吧(上图圈出来的部分)
- 如果需要的话开启事务(和传播属性相关,我们后面会提到)
- 执行业务逻辑
- 如果发生异常则会滚事务
- 如果正常执行则提交事务
「所以当发生异常需要会滚的时候,我们一定不要自己把异常try catch掉,不然事务会正常提交」
TransactionAspectSupport#createTransactionIfNecessary
当开启事务的时候,可以看到各种传播属性的行为(即@Transactional方法调用@Transactional方法会发生什么?)
AbstractPlatformTransactionManager#getTransaction
Spring事务的传播行为在Propagation枚举类中定义了如下几种选择
「支持当前事务」
- REQUIRED :如果当前存在事务,则加入该事务。如果当前没有事务,则创建一个新的事务
- SUPPORTS:如果当前存在事务,则加入该事务 。如果当前没有事务, 则以非事务的方式继续运行
- MANDATORY :如果当前存在事务,则加入该事务 。如果当前没有事务,则抛出异常
「不支持当前事务」 - REQUIRES_NEW :如果当前存在事务,则把当前事务挂起,创建一个新事务
- NOT_SUPPORTED :如果当前存在事务,则把当前事务挂起,以非事务方式运行,
- NEVER :如果当前存在事务,则抛出异常
「其他情况」 - NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来执行 。如果当前没有事务,则该取值等价于REQUIRED
以NESTED启动的事务内嵌于外部事务中 (如果存在外部事务的话),此时内嵌事务并不是一个独立的事务,它依赖于外部事务。只有通过外部事务的提交,才能引起内部事务的提交,嵌套的子事务不能单独提交
事务失效的场景有哪些?
因为我们经常使用声明式事务,如果一步消息就会导致事务失效,所以我们就从源码角度来盘一下事务为什么失效
异常被你try catch了
首先就是我们上面刚提到的,「异常被你try catch了」。因为声明式事物是通过目标方法是否抛出异常来决定是提交事物还是会滚事物的
自调用
当自调用时,方法执行不会经过代理对象,所以会导致事务失效
// 事务失效
@Service
public class UserServiceV2Impl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addUser(String name, String location) {
doAdd(name);
}
@Transactional
public void doAdd(String name) {
String sql = "insert into user (`name`) values (?)";
jdbcTemplate.update(sql, new Object[]{name});
throw new RuntimeException("保存用户失败");
}
}
我们可以通过如下三种方式来解决自调用失效的场景
「1.@Autowired注入代理对象,然后调用方法」
// @Service
public class UserServiceV3Impl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private UserService userService;
@Override
public void addUser(String name, String location) {
userService.doAdd(name);
}
@Override
@Transactional
public void doAdd(String name) {
String sql = "insert into user (`name`) values (?)";
jdbcTemplate.update(sql, new Object[]{name});
throw new RuntimeException("保存用户失败");
}
}
「2.从ApplicationContext获取代理对象,然后调用方法」
@Service
public class UserServiceV4Impl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ApplicationContext applicationContext;
@Override
public void addUser(String name, String location) {
UserService userService = applicationContext.getBean(UserService.class);
userService.doAdd(name);
}
@Override
@Transactional
public void doAdd(String name) {
String sql = "insert into user (`name`) values (?)";
jdbcTemplate.update(sql, new Object[]{name});
throw new RuntimeException("保存用户失败");
}
}
「3.进行如下设置@EnableAspectJAutoProxy(exposeProxy = true),从AopContext中获取代理对象,然后调用方法」
@Service
public class UserServiceV5Impl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addUser(String name, String location) {
UserService userService = (UserService) AopContext.currentProxy();
userService.doAdd(name);
}
@Override
@Transactional
public void doAdd(String name) {
String sql = "insert into user (`name`) values (?)";
jdbcTemplate.update(sql, new Object[]{name});
throw new RuntimeException("保存用户失败");
}
}
非public方法导致事务失效
我们先来猜一下为什么非public方法会导致事务失效?
「难道是因为非public方法不会生成代理对象?」
我们给一个非public方法加上@Transactional,debug到如下代码看一下是否会生成代理对象
AbstractAutoProxyCreator#wrapIfNecessary
「结论是不会生成代理对象,那为什么不会生成代理对象呢?」
应该就是不符合Pointcut的要求了呗,我们在前面已经提到了事务对应的Pointcut为TransactionAttributeSourcePointcut
TransactionAttributeSourcePointcut#matches
matches方法返回false,为什么会返回false呢?
一直debug发现是如下代码导致的
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
即public方法能正常生成代理对象,而非public方法因为不符合Pointcut的要求,根本就不会生成代理对象
异常类型不正确,默认只支持RuntimeException和Error,不支持检查异常
「为什么不支持检查异常呢?」
拿出我们上面分析过的代码
当执行业务逻辑发生异常的时候,会调用到TransactionAspectSupport#completeTransactionAfterThrowing方法
可以看到对异常类型做了判断,根据返回的结果来决定是否会滚事务,会调用到如下方法进行判断
RuleBasedTransactionAttribute#rollbackOn
如果用户指定了回滚的异常类型,则根据用户指定的规则来判断,否则用默认的规则
DefaultTransactionAttribute
默认的规则为只支持RuntimeException和Error
我们可以通过@Transactional属性指定回滚的类型,一般为Exception即可
@Transactional(rollbackFor = Exception.class)
文章转自公众号:Java识堂