spring @Transactional注解 事务不回滚的两个原因
一是Service类内部方法调用
二是try...catch异常,我们自定义的异常不是 RuntimeException
1. Service类内部方法调用
大概就是 Service 中有一个方法 A,会内部调用方法 B, 方法 A 没有事务管理,方法 B 采用了声明式事务,通过在方法上声明 Transactional 的注解来做事务管理。示例代码如下:
@Service
public class RabbitServiceImpl implements RabbitService {
@Autowired
private RabbitDao rabbitDao;
@Autowired
private TortoiseDao tortoiseDao;
@Override
public Rabbit methodA(String name){
return methodB(name);
}
@Transactional(propagation = Propagation.REQUIRED)
public boolean methodB(String name){
rabbitDao.insertRabbit(name);
tortoiseDao.insertTortoise(name);
return true;
}
}
单元测试代码如下:
public class RabbitServiceImplTest {
@Autowired
private RabbitService rabbitService;
// 事务未开启
@Test
public void testA(){
rabbitService.methodA("rabbit");
}
// 事务开启
@Test
public void testB(){
rabbitService.methodB("rabbit");
}
}
从上一节中可以看到,声明式事务是通通过AOP动态代理实现的,这样会产生一个代理类来做事务管理,而目标类(service)本身是不能感知代理类的存在的。
对于加了@Transactional注解的方法来说,在调用代理类的方法时,会先通过拦截器TransactionInterceptor开启事务,然后在调用目标类的方法,最后在调用结束后,TransactionInterceptor 会提交或回滚事务,大致流程如下图:
总结,在方法 A 中调用方法 B,实际上是通过“this”的引用,也就是直接调用了目标类的方法,而非通过 Spring 上下文获得的代理类,所以事务是不会开启的。
2. try...catch异常
在一段业务逻辑中对数据库异常进行了处理,使用了try...catch子句捕获异常并throw了一个自定义异常,这种情况导致了事务未回滚,示例代码如下:
@Transactional(propagation = Propagation.REQUIRED)
public boolean methodB(String name) throws BizException {
try {
rabbitDao.insertRabbit(name);
tortoiseDao.insertTortoise(name);
} catch (Exception e) {
throw new BizException(ReturnCode.EXCEPTION.code, ReturnCode.EXCEPTION.msg);
}
return true;
}
上面代码中的声明式事务在出现异常的时候,事务是不会回滚的。在代码中我虽然捕获了异常,但是同时我也抛出了异常,为什么事务未回滚呢?猜测是异常类型不对,于是开始查询原因,翻看了Spring的官方文档,找到了答案。下面是翻译自Spring官网:
看完官方文档这节内容找到了问题的答案,原来是因为我们自定义的异常不是 RuntimeException。我的解决办法是,在注解@Transactional中添加 rollbackFor={BizException.class}。可能你会问我为什么不将自定义异常修改为继承RuntimeException,因为我需要BizException是一个checked 异常。