记一次实际开发过程中遇到事务报错问题 Transaction synchronization is not active

一:问题场景

  在一次http请求的后台接口中返回结果中出现了这个错误信息“Transaction synchronization is not active”,意思是“事务同步器没有激活”,但是被调用的接口已经添加了@Transactional注解,所以百思不得不得其解为什么还会报这个错误,那么是什么原因引起了这个异常呢?自己通过百度大神博客终于找到一篇相似的文章解决了自己的问题,今天就是把解决方案记录下来为以后再出现类似情况提供解决方案。

二:出现原因

  Google搜索一番之后,发现了这篇文章Spring的TransactionEventListener,文中提到了可能出现这个错误信息的一种情况:

  @EventListener
  public void afterRegisterSendMail(MessageEvent event) {
    // Spring 4.2 之前
    TransactionSynchronizationManager.registerSynchronization(
      new TransactionSynchronizationAdapter() {
        @Override
        public void afterCommit() {
          internalSendMailNotification(event);
      }
    });
  }
  上面的代码将在事务提交后执行.如果在非事务context中(就是方法未开启spring事务)将抛出java.lang.IllegalStateException: Transaction synchronization is not active

  于是找到自己代码中的TransactionSynchronizationManager部分,果然在当前http请求链路中的一个接口里发现了类似的代码:

  TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
    @Override
    public void afterCommit() {
      try {
        byte[] bytes = JSON.toJSONBytes(task, new SerializeConfig());
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setMessageId(idWorker.nextStringId());
        Message message = new Message(bytes, messageProperties);
        salaryFileMsgProducer.sendSalaryFileMsg(message);
      } catch (Exception e) {
        log.error(e.getMessage());
        e.printStackTrace();
      }
    }
  });

  想必是执行这段代码的时候报的异常,但是上面那篇文章说了引起这个异常的原因是“在非事务context中注册同步器”,难道当前事务没有开启?我们的业务代码一般都是使用spring的注解@Transactional来开启事务,那么去看一下开启事务的代码片段。

三:根本原因
  我在调用TransactionSynchronizationManager.registerSynchronization()的方法体上找到了@Transactional注解,方法代码如下,省略具体实现:

  @Transactional(rollbackFor = Exception.class)
  public Boolean creatSalaryMonthStatistic(SalaryMonthlyStatistic salaryMonthlyStatistic){
    ....
  }
  这就奇怪了,明明方法上已经添加@Transactional注解开启事务,为什么事务开启失败,带着这个疑问再次求助了Google,发现了这篇博客spring的service类调用自己方法事务无效,作者也遇到了事务无效的问题,其中总结了事务有效性相关的几点重要信息:

  1.在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。

  2.@Transactional 注解只能应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

  3.注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。(spring配置文件中,开启声明式事务)

  4.通过 元素的 “proxy-target-class” 属性值来控制是基于接口的还是基于类的代理被创建。如果 “proxy-target-class” 属值被设置为 “true”,那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果 “proxy-target-class” 属值被设置为 “false” 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。

  5.Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。

  6.@Transactional的事务开启 ,或者是基于接口的 或者是基于类的代理被创建。所以在同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的。

特别注意第6点:同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的。这一点引起了我的注意,可能我的加了注解@Transactional的creatSalaryMonthStatistic方法也是被类里另一个没有开启事务的方法调用,如果真的是这样,所有一切都解释的通了。所以带着这个思路检查了一遍自己的代码,

  果然,creatSalaryMonthStatistic方法被一个没加事务的接口方法调用,从而整个creatSalaryMonthStatistic方法都没有事务效果:

  //此接口方法未加事务
  public Boolean batchInitSalaryFileDataTask4Tenant(String tenantId){
    .....
    salaryFileInitTaskBiz.createSalaryFileDateInitTask4Staff(file);
    .....
  }
四:解决方案
  知道了引起问题的原因,解决方法也比较简单,有好几种方法都可以解决这个问题,可以根据个人需要选择一种解决方法,本文列出其中三种:
  1.比较方便、暴力的一种方法就是直接在最外层接口方法中添加事务注解,也就是给本文中的batchInitSalaryFileDataTask4Tenant方法加上注解即可。
  2.也可以通过代理类调用creatSalaryMonthStatistic方法,代理类中包含了事务逻辑,这样也能实现事务功能。
  3.还可以直接将creatSalaryMonthStatistic这个方法放到另外一个类中。

posted @ 2020-07-07 19:42  MR饶  阅读(4994)  评论(0编辑  收藏  举报