场景:Excel导入人口信息。需要先删除重复的,然后循环插入一条条信息(due to 许多原因,整个流程最开始是放在一个方法里的)。由于导入的一条数据涉及多个表格的查询和插入,会存在需要回滚的情况。e.g. A数据先插入,B数据后插入。 由于B数据插入错误,A也必须回滚到插入前的状态。但一直没有成功。刚好对spring boot的事务回滚不是很了解,现在记录下学习的情况。
一 、事务的两种管理机制 a. 声明式事务基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TX和AOP的xml配置文件方式,二种就是基于@Transactional 注解了。
b.编程式事务是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强。如下所示(手动回滚):
try {
//执行代码
}
return errorList;
} catch (Exception e) {
e.getMessage()
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackonly();
}
二、 回滚失败的几种情况
先说这个是因为我当时直接照葫芦画瓢加了个注释,以为就能用了。后来慢慢倒查信息,学到了比较完整的事务管理。所以先记录这个东西。
由于我仅仅需要对单个插入进行回滚,并不是整个插入流程(从删除重复到全部插入完毕)。所以最开始我把插入循环提取出来,放在同一个类中并在方法上作了注释。引出了第一个回滚失败的情况。
这个是我最开始犯的错误。为什么同一个类中的方法被调用 @Transactional就会失效?
这跟spring AOP(埋个点,日后学习下AOP)有关。由于使用spring aop代理,事务的有效范围仅在aop代理能找到的情况。但方法调用同一个类中的内部方法并不会经过aop代理,因此@Transactional失效。
如果整个方法确实需要放在一个类中,这该怎么办呢?只需要在开头声明一下就行了。
public class importInfo{
// 自己声明自己,哈哈哈
private importInfo importInfo;
}
自己声明了自己以后,就会生成对象在AOP中被调用,@Transactional就有用了。
2. 异常被你的 catch“吃了”导致 @Transactional 失效在意识到第一个错误后,我把循环插入的方法提取出来,扔到了一个工具类中。但仍然不能回滚。这就引出了第二个原因。由于不可抗力的原因,我的循环体中,必须用try catch来捕获异常并返回一个列表。而@Transactional 的触发也是需要有异常报出。这看起来并不冲突,却是“无解”的事情。 @Transactional 是在方法报错后,对整个方法进行回滚。但try catch会把代码块的报错合理化。即,代码报错异常后被catch捕获然后返回对应的措施。在整个方法层面讲,就是没有报错。
那,怎么解决呢?
最开始我和同事打算直接在catch中扔出来一个异常(throw new Exception();)发现IDE直接报错。这是因为这是一个编译时就报错的异常,而不是运行时报错的异常。我的同事,是个狠人,直接int i = 1/0; 整个理论上是可以的,但跟 throw new RuntimeException()一样,属于走bug的方法且无法返回return。
正确的解决方案是,在catch中手动事务回滚
catch (Exception e) {
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackonly();
return exampleList;
}
3. @Transactional 注解属性 rollbackFor 设置错误
这个是我在测试@Transactional时注意到的一个方面。当你给@Transactional设定回滚错误类型时,需要注意到这点。你希望发生什么类型的异常时回滚。一般来说,不写就是默认Exception.class。这是最高类型的异常。
4. @Transactional 应用在非 public 修饰的方法上 5. @Transactional 注解属性设置错误这个涉及事务的传播机制
6. 数据库引擎不支持事务 三、事务的传播机制①、PROPAGATION_REQUIRED :required , 必须。默认值,A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务。
②、PROPAGATION_SUPPORTS:supports ,支持。A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行。
③、PROPAGATION_MANDATORY:mandatory ,强制。A如果有事务,B将使用该事务;如果A没有事务,B将抛异常。
④、PROPAGATION_REQUIRES_NEW :requires_new,必须新的。如果A有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务。
⑤、PROPAGATION_NOT_SUPPORTED :not_supported ,不支持。如果A有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行。
⑥、PROPAGATION_NEVER :never,从不。如果A有事务,B将抛异常;如果A没有事务,B将以非事务执行。
⑦、PROPAGATION_NESTED :nested ,嵌套。A和B底层采用保存点机制,形成嵌套事务。
带你读懂Spring 事务——事务的传播机制
这篇文章很详细的讲解了这7种的传播机制以及他们面对回滚时的不同个反应。



