在开发过程中,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();