在开发过程中,Spring的声明式事务可以通过一个简单的@Transactional注解,就让我们轻松进行事务处理。我们知道Spring事务基于AOP,采用动态代理实现,虽然使用简单,但是在实际场景中,我们也会遇到一些坑。这简单记录了一些常见的情况。
一.常见事务失效问题
1.@Transactional属性设置问题
@Transactional
的rollbackFor用于指定能够触发事务回滚的异常类型,可以指定多个,用逗号分隔。rollbackFor
默认值为UncheckedException,包括了RuntimeException和Error.
当我们直接使用@Transactional
不指定rollbackFor
时,Exception及其子类都不会触发回滚。所以需要根据情况而定,一般我们会所使用:@Transactional
(rollbackFor=Exception.class),这样,Exception及其子类都会触发回滚。
Exception结构图:
2.一个没有事务的方法调用本类的另一个有事务的方法导致事务失效
如下这种情况:
public class TranTestService {
@Autowired
private CuponConfirmDao confirmDao;
@Autowired
private TranTest2Service tranTest2Service;
// 方法 A
public int updateStatusT(CuponModel cuponModel) {
System.out.println("更新方法1执行开始");
// 调用本类的方法 B
int count = this.updateOrderStatusT(cuponModel);
System.out.println("更新方法1执行结束");
return count;
}
// 方法 B
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,rollbackFor=Exception.class)
public int updateOrderStatusT(CuponModel cuponModel) {
int count = confirmDao.updateOrderStatusT(cuponModel); // 更新表操作
int sum = 6/0;// 模拟异常
int count1 = confirmDao.updateCupoNoStateT(cuponModel); // 更新表操作
return count + count1;
}
}
以上是部分实验代码,小结一下:
a. 一个没有事务的方法,调用同一个类下面的另一个有事务的方法——事务失效。
b. 一个没有事务的方法,调用另一个类下面一个有事务的方法——事务有效。
c. 一个有事务的方法,调用同一个类下面的另一个没有事务的方法——事务有效。
d. 一个有事务的方法,调用另一个类下面一个没有事务的方法——事务有效。
e. 两个方法都有事务—— 两种调用方式事务都有效。
3.try catch异常处理导致事务失效
示例代码如下:
@Service
public class TranTest2Service {
@Autowired
private CuponConfirmDao confirmDao;
// 方法 B
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,rollbackFor=Exception.class)
public int updateOrderStatusT(CuponModel cuponModel) {
try {
confirmDao.updateOrderStatusT(cuponModel); // 更新表操作
int sum = 1 / 0;// 模拟异常
confirmDao.updateCupoNoStateT(cuponModel); // 更新表操作
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
由此可知,在spring中如果某个业务方法被一个
try{
//bisiness logic code
} catch(Exception e) {
//handle the exception
}
整个包裹起来,则这个业务方法也就等于脱离了spring事务的管理,因为没有任何异常会从业务方法中抛出!全被捕获并吞掉,导致spring异常抛出触发事务回滚策略失效。
解决方案:
方案一 、外抛
把异常往外抛(不加cry catch),然后在外层 try catch(比如:service层抛出,controller层try catch处理),但是问题来了,出现异常之后,service层代码就停止运行了,如果我们希望流程能继续走下去,那就使用方案二(最佳)。
方案二、手动回滚
示例代码:
public class TranTest2Service {
@Autowired
private CuponConfirmDao confirmDao;
// 方法 B
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,rollbackFor=Exception.class)
public int updateOrderStatusT(CuponModel cuponModel) {
try {
confirmDao.updateOrderStatusT(cuponModel); // 更新表操作
int sum = 1 / 0;// 模拟异常
confirmDao.updateCupoNoStateT(cuponModel); // 更新表操作
} catch (Exception e) {
e.printStackTrace();
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return 0;
}
}
在catch代码块里加上回滚语句:
try {
// 操作代码块
} catch (Exception e) {
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
加上该语句即可:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();