spring 的相关的事务失效的问题总结

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring 的相关的事务失效的问题总结相关的知识,希望对你有一定的参考价值。


  这一个问题,真的是很有意思。之前不了解的时候觉得会用就行了。但是真的不知道里边这么多坑。不过这些坑都真的很有意思。它推着你去了解底层。推着你看源码,了解实现原理。

  也是因为我在最近的开发中遇到了一些问题,自己进行排查,然后谷歌了很多很多的文章。这篇文章算是一个总结吧。希望可以做到的是,关于事务失效的问题看这一篇文章就够了。

  尽可能的做到全一点,篇幅可能会大一点。这里为了省事,一些demo就不自己敲了,借鉴一下比人的。

  ~ps 如果你是因为事务失效问题,看到这篇文章没有解决的,亲请给我留言,我可能会对你失效的原因很感兴趣,我们一块探讨探讨,然后争取解决了放在这里。

 

 

# # 事务失效分类、

  首先事务失效的原因有这几个层面:

  • 代码层面
  • 数据库层面
  • 框架层面

 

# #  代码层面(这个也是有很多个原因)

   这里先列一下,然后再逐个展开:

  • 没有加@Transactional注解
  • 因为try catch原因
  • 因为没加@Service,没有被 spring 管理
  • 因为方法不是public d
  • 因为事务的传播行为导致事务失效。

 

  • 比方说我们没有加 @Transactional注解,注意这个注解,就是加在 service 层的,不要乱加,再注意一点,如果能确定加在方法上就不要加在类上。因为根据底层的实现。你给加在类上,就意味着给全部的方法都加事务,再根据底层的实现,事务是用 AOP 实现的,动态代码要损失一点性能。

         解决方案:注意这是 service 层的方法,之前不懂事,我也加在 dao层过。

           

spring

 

  • ry catch 吞掉了异常,接下来看一个例子。这个例子是事务失效的例子

   注意这里的例子,我持久层用的是 jpa,这里的 save 就相当于是一个insert操作、

@Transactional
public void transactionalTest()
try
TmcFinanceServiceFeeCheckLogEntity entity = new TmcFinanceServiceFeeCheckLogEntity();
entity.setCompanyNo("555666");
entity.setDefineNo("6666666");
entity.setServiceType("03");
entity.setServiceTypeDetail("031");
entity.setServiceNumTotal(1);
entity.setTotalNo("2222");
entity.setStatisticsTimeStart(new Timestamp(System.currentTimeMillis()));
entity.setStatisticsTimeStop(new Timestamp(System.currentTimeMillis()));
entity.setCheckType("1");
entity.setCheckStatus("2");
entity.setStartTime(new Timestamp(System.currentTimeMillis()));
entity.setStatisticsInsideDate("20200405");
tmcFinanceServiceFeeCheckLogRepository.save(entity);
int temp = 1/0;
catch (Exception e)
e.printStackTrace();

 

    有的人,特别热衷于 try catch ,像例子里边的 try catch 就是非常多余的。我们程序应该是配置全局异常。而不是在这里手动的去补货异常。甚至,这个try catch 起到了画蛇添足的作用。因为它把异常吞掉了,而这个加了事务注解的方法就看不到异常了。spring 的事务 @Transactional不做任何配置 默认是对抛出的unchecked异常回滚,checked异常不会回滚。显然,这里你 catch,已经是检查异常了,既然你 catch,那你就自己做处理好了。我事务不管了。所以  try catch会让事务失效。

  解决方案:try catch 在这里是多余的,仅仅需要去掉就可以了。就是能不用则不用。 那如果遇到了一定要用的情况怎么办?那就是自己杀的自己埋。既然你都已经捕获了,就自己处理。在 catch里边 自己进行事务的回滚。代码如下:注意看 catch里边的就可以了,这是手动回滚事务的操作。

@Transactional
public void transactionalTest()
try
TmcFinanceServiceFeeCheckLogEntity entity = new TmcFinanceServiceFeeCheckLogEntity();
entity.setCompanyNo("555666");
entity.setDefineNo("6666666");
entity.setServiceType("03");
entity.setServiceTypeDetail("031");
entity.setServiceNumTotal(1);
entity.setTotalNo("2222");
entity.setStatisticsTimeStart(new Timestamp(System.currentTimeMillis()));
entity.setStatisticsTimeStop(new Timestamp(System.currentTimeMillis()));
entity.setCheckType("1");
entity.setCheckStatus("2");
entity.setStartTime(new Timestamp(System.currentTimeMillis()));
entity.setStatisticsInsideDate("20200405");
tmcFinanceServiceFeeCheckLogRepository.save(entity);
int temp = 1/0;
catch (Exception e)
// 重点看这里
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
e.printStackTrace();

  

  • 没有加 @service

  例子如下:

// @Service
public class OrderServiceImpl implements OrderService

@Transactional
public void updateOrder(Order order)
// update order


   这个问题,说白了,事务是由spring帮我们实现的,那么想要使用,就必须把类放在spring 容器里边来管理。如果你的类没有加 @service 这就相当于没有买人家的保险,还想让人家赔钱,这是不可能的。 

 

  • 因为方法不是public 的

关于@Transactional的说明,Spring 有一段描述关于方法可见性:

Method visibility and @Transactional
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

@Transactional注解只对代理类时的public方法有效,被protected、private、package-visible修饰的方法使用@Transactional注解无效,对这类方法使用事务注解,推荐使用AspectJ进行事务管理。Spring框架虽然提升了效率,偶尔也会产生意外的问题,且行且研究。

 

  • 因为事务的传播行为导致事务失效。

关于这一点Spring Transactional官方说明如下:

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

在代理下(默认或当配置为proxy-target-class="true"),只有当前代理类的外部方法调用注解方法时代理才会被拦截。事实上,这意味着:一个目标对象的方法调用该目标对象的另外一个方法,即使被调用的方法已使用了@Transactional注解标记,事务也不会有效执行。

 

  看一个因为事务传播原因导致的事务失效的例子:

  描述transactionalTest ()方法调用了 methodTest (),methodTest 已经加了事务注解。我们想当然的以为。调用的方法加了事务,里边出现了错误应该会发生回滚的。 而实际上不会回滚。

@Override
public void transactionalTest()
//调用一个加了事务注解,但是会在运行过程中报错
methodTest();



@Transactional
public void methodTest()
// 故意写一个异常,看会不会回滚
TmcFinanceServiceFeeCheckLogEntity entity = new TmcFinanceServiceFeeCheckLogEntity();
entity.setCompanyNo("666666");
entity.setDefineNo("6666666");
entity.setServiceType("03");
entity.setServiceTypeDetail("031");
entity.setServiceNumTotal(1);
entity.setTotalNo("2222");
entity.setStatisticsTimeStart(new Timestamp(System.currentTimeMillis()));
entity.setStatisticsTimeStop(new Timestamp(System.currentTimeMillis()));
entity.setCheckType("1");
entity.setCheckStatus("2");
entity.setStartTime(new Timestamp(System.currentTimeMillis()));
entity.setStatisticsInsideDate("20200405");
tmcFinanceServiceFeeCheckLogRepository.save(entity);
int temp = 1/0;

 

 对于这个失效的处理方式就是,在调用的方法上也添加一个@Transactional ,也就是调用者之前没有事务,而被调用者有事务,没有事务的调用者调用有事务的被调用者,会发生事务失效。 

  这问题,我看别人的文章说,两个地方都添加事务注解,下边的被调用的方法的事务,仍然会失效,但是从我的测试用例来看,并没有失效。这里大家遇到可以咱们沟通一下,看看是不是因为事务管理器的差异造成的。

 

# # 数据库层面的原因

  这个比较好排查,只需要知道自己使用的数据库(引擎)是否支持事务。举个例子:mysql 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。

  使用其他的数据库的自查。

 

# # 框架层面的

  这个是我遇到的问题,就是上边的情况,都不是我遇到的。

以上是关于spring 的相关的事务失效的问题总结的主要内容,如果未能解决你的问题,请参考以下文章

盘点spring事务失效的情况

面试官:导致Spring事务失效,常见的情况有哪些?| Spring系列第51篇

为啥Spring事务失效了,你踩坑了吗?

259期面试官:Spring事务失效的场景有哪些?如何解决?

面试官:Spring事务失效的场景有哪些?如何解决?

记录一次spring事务失效问题