1.抛出检查异常导致事务不能正确回滚
检查异常:是指编译器要检查这类异常,检查的目的一方面是因为该类异常的发生难以避免,另一方面就是让开发者去解决掉这类异常,所以称为必须处理(try ...catch)的异常。如果不处理这类异常,集成开发环境中的编译器一般会给出错误提示。如SQLException,IOException,ClassNotFoundException 等。
非检查型异常:是指编译器不会检查这类异常,不检查的则开发者在代码的编辑编译阶段就不是必须处理,这类异常一般可以避免,因此无需处理(try ...catch)。如果不处理这类异常,集成开发环境中的编译器也不会给出错误提示。比如数组越界、访问null对象,这种错误你自己是可以避免的。编译器不会强制你检查这种异常。
- 原因:Spring 默认只会回滚非检查异常
- 解决:配置 rollbackFor 属性
@Transactional(rollbackFor = Exception.class)
2.业务方法内自己 try-catch 异常导致事务不能正确回滚
-
原因:事务通知只有捕捉目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉。比如我们代码中try{}catch(){}后没有抛出异常。
-
解决:异常原样抛出
- 在 catch 块添加
throw new RuntimeException(e);
- 在 catch 块添加
-
解决:手动设置 TransactionStatus.setRollbackOnly()
- 在 catch 块添加
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
- 在 catch 块添加
3.AOP 切面顺序导致导致事务不能正确回滚
原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常…
-
解决方法:在切面里面抛出异常、增加手动回滚。
-
调整切面顺序,在 MyAspect 上添加 @Order(Ordered.LOWEST_PRECEDENCE - 1),让自定义切面晚于事物执行。
4.非public方法导致的事务失效
- 解法1:改为 public 方法
- 解法2:添加 bean 配置如下(不推荐)
@Bean public TransactionAttributeSource transactionAttributeSource() { return new AnnotationTransactionAttributeSource(false); }
5.父子容器导致的事务失效
现在配置了父子容器,WebConfig 对应子容器,AppConfig 对应父容器,发现事务依然失效
- 原因:子容器扫描范围过大,把未加事务配置的 service 扫描进来
- 解法1:各扫描各的,不要图简便
- 解法2:不要用父子容器,所有 bean 放在同一容器
6.调用本类方法导致传播行为失效
- 原因:本类方法调用不经过代理,因此无法增强(bar()方法是代理对象调用的,foo中方法是对象本身的调用)
- 解法1:依赖注入自己(代理)来调用
- 解法2:通过 AopContext 拿到代理对象,来调用
七.@Transactional 没有保证原子行为
@Service
public class Service7 {
private static final Logger logger = LoggerFactory.getLogger(Service7.class);
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
int fromBalance = accountMapper.findBalanceBy(from);
logger.debug("更新前查询余额为: {}", fromBalance);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
public int findBalance(int accountNo) {
return accountMapper.findBalanceBy(accountNo);
}
}
八.@Transactional 方法导致的 synchronized 失效
针对上面的问题,能否在方法上加 synchronized 锁来解决呢?
@Service
public class Service7 {
private static final Logger logger = LoggerFactory.getLogger(Service7.class);
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public synchronized void transfer(int from, int to, int amount) {
int fromBalance = accountMapper.findBalanceBy(from);
logger.debug("更新前查询余额为: {}", fromBalance);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
public int findBalance(int accountNo) {
return accountMapper.findBalanceBy(accountNo);
}
}
答案是不行,原因如下:
- synchronized 保证的仅是目标方法的原子性(因为实际调用过程是代理对象调用方法),环绕目标方法的还有 commit 等操作,它们并未处于 sync 块内
- 解法1:synchronized 范围应扩大至代理方法调用
- 解法2:使用 select … for update (数据库的锁)替换 select