1. 抛出检查异常导致事务不能正确回滚
1.1 原因
1.2 解决方法
- 配置rollbackFor属性使得检查异常也会回滚
- 意思是只要这个事务方法抛出了Exception类型的异常,都会回滚事务。
@Transactional(rollbackFor = Exception.class)
2. 业务方法内使用了try-catch处理异常导致事务不能回滚
2.1 原因
- 事务是基于环绕通知的。事务通知只有捕捉到目标方法抛出的异常,才会执行相应的回滚处理。
- 但是目标方法内直接将异常处理了,事务通知就感知不到有异常,会认为事务正常结束。
2.2 解决方法
- 方式一:在catch块中再将异常抛出,让事务通知能够感知到异常
- 方式二:在catch模块中手动设置事务进行回滚
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
3. aop切面顺序导致事务不能正确回滚
@Service
public class Service3 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
}
}
@Aspect
public class MyAspect {
@Around("execution(* transfer(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
LoggerUtils.get().debug("log:{}", pjp.getTarget());
try {
return pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
3.1 原因
- 对于上述代码,我们自定义的通知也对事务方法进行了增强。而且在我们自定义的通知中捕捉了异常。
- 虽然事务增强方法优先级是最低的,但是我们自定义的增强方法如果不设置优先级,那么它的优先级也是最低的。
- 同样都是最低的优先级,Spring会选择自定义增强方法放在内层,将事务增强方法放在外层
- 所以当内层自定义增强方法将异常捕捉了,那么外层的事务增强方法就无法感知到异常,自然也不会回滚事务
3.2 解决方法
- 方法一:自定义增强也应该将异常抛出,而不是捕捉
- 方法二:调整切面顺序。将自定义增强方法的优先级调高一点
- 在MyAspect上添加@Order(Ordered.LOWEST_PRECEDENCE - 1)
4. 非public方法导致事务失效
4.1 原因
- Spring为方法创建代理、添加事务通知的前提条件都是这个方法时public的
- 如果这个方法不是public的,那么即使这个方法上有@Transactional注解,也不会进行事务增强
4.2 解决方法
- 方法一:将方法该为public方法
- 方法二:向容器中添加一个bean
@Bean
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource(false);
}
5. 父子容器导致事务失效
5.1 原因
- 配置了父子容器,但是子容器扫描的范围过大,把未加事务配置的service扫描进来了。(但只有父容器中添加了@EnableTransactionManagement注解)
- 导致子容器调用service的时候,它会优先调用自己容器中的service实例,如果没有才会去调用父容器中service。所以它只能调用自己容器中没有开启事务增强的service了。
5.2 解决方法
- 方式一:父子容器扫描的包尽量不要重复
- 方式二:不使用父子容器,所有的bean放在同一个容器中(Springboot就是采用这种方式)
6. 调用本类方法导致传播行为失效
@Service
public class Service6 {
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
6.1 原因
- 本类方法调用本类方法,则内部调用的本类方法是没有经过代理的原生方法。因此无法增强内部调用的本类方法
6.2 解决方法
@Service
public class Service6 {
@Autowired
private Service6 proxy; // 本质上是一种循环依赖
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
System.out.println(proxy.getClass());
proxy.bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
- 方法二:通过AopContext拿到代理对象,来调用代理方法(需要则配置类上添加@EnableAspectJAutoProxy(exposeProxy = true) 注解)
@Service
public class Service6 {
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
((Service6) AopContext.currentProxy()).bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
- 方法三:通过CTW(编译时织入),LTW(加载时织入)实现功能增强
7. @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);
}
}
7.1 原因
- 上述问题是,查询数据库和更新数据库之间不是原子操作。会导致一些逻辑错误(比如没钱了还能向其他用户转账)
7.2 解决方法
- 方法一:在查询语句时就对查询到的结果记录加锁,防止别人对我们查询的记录进行了修改而我们还感知不到。
8. 在事务方法上加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);
}
}
8.1 原因
- synchronized加锁在这里只能锁住目标方法,但是事务提交语句还是在事务增强方法中。所以还是不能保证从事务开始到事务提交这整个过程是原子性的。
8.2 解决方法
- 将synchronized范围扩大(比如我们在controller层加synchronized)
- 使用select ... for update在查询时加排他锁。