Spring事务失效场景原理及解决方案
原理
Spring事务使用AOP实现,核心原理是在程序执行前后动态代理,在方法执行前开启一个事务,在方法执行后根据方法执行结果决定事务是提交还是回滚。但是在以下场景中,Spring事务可能失效:
- 在事务方法外部调用另一个事务方法时,当前事务被挂起,新的事务启动,第二个事务抛出异常回滚,当前事务并不会回滚。
- 在catch语句中抛出异常,因为异常被处理了,程序并不会抛出异常,当前事务也不会回滚。
解决方案
Transactional注解传播行为
解决第一种场景的方案是更改事务的传播行为。在Transaction声明之后加上propagation属性,这个属性可以设置当前方法和新开启事务方法的传播行为。如果是REQUIRED,表示当前方法和被调用的方法在同一个事务中,如果前一个事务失败,整个事务都将回滚。如果是REQUIRES_NEW,表示前一个方法和新开启的方法使用不同的事务。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
//...
}
try-catch中抛出异常
解决第二种场景的方案是在catch中主动抛出异常,这样异常就不会被处理,事务也能够正常回滚。同时还要注意:为了保证事务正常,异常类型要抛出RuntimeException或者其他继承自RuntimeException的异常。
try {
//...
} catch (Exception e) {
// 处理异常
throw new RuntimeException(e);
}
示例
示例1
@Transactional
public void methodA() {
try {
methodB();
} catch (Exception e) {
// 异常被处理了,当前事务并不会回滚
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 方法B执行出错,但是异常不会传播到方法A
throw new RuntimeException("故意抛出异常");
}
在这个例子中,方法A和方法B都使用了@Transactional注解,它们都有着相同的事务。但当方法B执行出错时,并不会回滚整个事务。原因是当前事务被挂起,新事务启动了,方法B的异常只会被新事务捕获,不会影响到之前的事务。
为了解决这个问题,可以把方法B的传播行为更改为REQUIRES_NEW,这样引入新的事务,保证即使方法B出错,它的事务也可以正常回滚。
示例2
@Transactional
public void methodA() {
try {
methodB();
} catch (Exception e) {
// 处理异常,当前事务并不会回滚
}
}
@Transactional
public void methodB() {
try {
// 方法B执行出错,异常被处理了,当前事务并不会回滚
throw new Exception("故意抛出异常");
} catch (Exception e) {
// 再次抛出异常,当前事务将回滚
throw new RuntimeException(e);
}
}
在这个例子中,当方法B执行出错时,异常被处理了,当前事务也不会回滚。为了解决这个问题,处理异常的catch中加入throw new RuntimeException(e)这一行代码,再次抛出异常,这样事务将会回滚。同时要注意,再次抛出异常的类型要继承自RuntimeException,这样才能保证异常可以被事务捕获。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring事务失效场景原理及解决方案 - Python技术站