首页 > 其他分享 >@Transactional千万不要这样用!!踩坑了你都可能发现不了!!!

@Transactional千万不要这样用!!踩坑了你都可能发现不了!!!

时间:2023-02-25 17:24:49浏览次数:46  
标签:事务 不了 Transactional 千万 id Propagation 方法 public

39ee-a8190c49d990912be28c50eee32fe5be

前阵子接手了一段同事之前的代码,里面用到了@Transactional注解,了解Spring的小伙伴肯定知道,@Transactional是Spring提供的一种控制事务管理的快捷手段。但是我这段程序在运行的时候,经常出现莫名其妙的问题,连夜研究了好久才搞清楚,在这里记录一下, 避免大家入坑。

1. 大家来找茬

在介绍具体问题之前,我把问题代码简化了一下,看大家能找到其中的问题吗?

问题代码1

下面的这段代码主要是想利用MySQL里面的行锁select for update,来实现简单的分布式锁。但是在实践过程中,发现这个锁好像并没有生效,而且在数据库的里面也没有查找对应transaction连接的信息。

@Component
@EnableScheduling
public class someService {
  
  @Scheduled(...)
  public doSomeWork() {
    // find some id by logic
    
    // process the related info
    doOtherWork(id);
  }
  
  @Transactional(isolation = Isolation.READ_COMMITTED)
  public void doOtherWork(id) {
    Info info = requestMapper.selectByPrimaryKeyForUpdate(id);
    doSomeFollowingProcess(info);
    ...
  }
}

问题代码2

下面代码分两个步骤,第一步会检查相关信息,第二步调用了一个transactional修饰的方法,完成一些基本工作;但在实践中,发现一个非常诡异的问题,在MainWork中,doSomeCheck执行时会抛出nullPointException,debug发现所有autowired进来的service均为空,注释掉doSomeCheck里面的内容后,继续往下执行,却发现doWork能够正常执行,所有的注入均没有问题。

@Component
public class MainWork {
  @AutoWired
  DetailWork detailWork
    
  public void workflow() {
    detailWork.doSomeCheck();
    detailWork.doWork();
  }
}

@Component
public class DetailWork {
  
  	@AutoWired
  	UsefulService usefulService;
  
  	@AutoWired
  	InfoService infoService;
  
    @Transactional(isolation = Isolation.READ_COMMITTED)
  	public void doWork() {
      usefulService.doSomeWork();
    }
  
  	void doSomeCheck() {
      infoService.getInfo();
    }
}

大伙看看能发现什么问题吗?

2. 关于@Transactional注解

Spring支持编程式事务管理声明式事务管理两种方式。

  • 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。

  • 声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后,根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需基于@Transactional注解的方式,便可以将事务规则应用到业务逻辑中

下图是调用@Transactional注解的方法时,Spring内部的时序图。简单来讲就是IOC容器初始化时,会生成@Transactional注解所在类的代理对象,然后实际执行中会通过AOP执行代理对象的方法,TransactionAdvisor会在方法调用前判断是否开启事务,在调用结束后,会判断是否提交或回滚事务。

Eithaa

深入研究代码,我们会发现TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。

protected TransactionAttribute computeTransactionAttribute(Method method,
    Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
}

此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。也就是说protected、private 修饰的方法上使用 @Transactional 注解会导致事务无效。

了解了@Transactional的原理之后,我们在回头看看之前的问题,会不会是使用方法不对导致的呢?

3. 拨云见日

问题代码1解析

下面的代码中,我们在同一个类里面调用了@Transactional修饰的方法,其实这样调用的话并没有用到Spring AOP生成的代理对象。从上面的时序图也可以看到,只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

@Component
@EnableScheduling
public class someService {
  
  @Scheduled(...)
  public doSomeWork() {
    // find some id by logic
    
    // process the related info
    doOtherWork(id);
  }
  
  @Transactional(isolation = Isolation.READ_COMMITTED)
  public void doOtherWork(id) {
    Info info = requestMapper.selectByPrimaryKeyForUpdate(id);
    doSomeFollowingProcess(info);
    ...
  }
}

那如何解决这种类内调用的问题呢? 很简单,可以使用applicationContext直接从IOC容器中将someService类取出来,然后再调用doOtherWork方法即可,这样就能用上Spring AOP生成的代理对象了

下面是更改之后的代码,更改之后发现事务生效了,问题解决!

@Component
@EnableScheduling
public class someService {
  
  @Autowired
  private ApplicationContext applicationContext;
  
  @Scheduled(...)
  public doSomeWork() {
    // find some id by logic
    
    // process the related info
    SomeService someService = applicationContext.getBean(someService.class);
    someService.doOtherWork(id);
  }
  
  @Transactional(isolation = Isolation.READ_COMMITTED)
  public void doOtherWork(id) {
    Info info = requestMapper.selectByPrimaryKeyForUpdate(id);
    doSomeFollowingProcess(info);
    ...
  }
}

问题代码2解析

下面的代码中,MainWork调用doSomeCheck的时候,会出现null的情况,原因是由于该方法不是public方法,会导致@Transactional调用失败。你可能会说这就是普通方法,跟@Transactional有什么关系?

需要注意的是,无论transactional注解在类上还是在方法上,IOC容器都会生成对应类的代理对象,然后使用代理对象去访问对应的方法。在这个例子里面, 调用doWork时一切正常,事务也会生效;但是调用doSomeCheck时,从之前的分析可以看到,由于方法不是public,此时事务管理器不会起作用,直接导致所有的autowired未完成注入。修改的方法也很简单,把doSomeCheck改成public就行了。

这个问题隐藏比较深一些,不清楚原理很难发现这个问题。

@Component
public class MainWork {
  @AutoWired
  DetailWork detailWork
    
  public void workflow() {
    detailWork.doSomeCheck();
    detailWork.doWork();
  }
}

@Component
public class DetailWork {
  
  	@AutoWired
  	UsefulService usefulService;
  
  	@AutoWired
  	InfoService infoService;
  
    @Transactional(isolation = Isolation.READ_COMMITTED)
  	public void doWork() {
      usefulService.doSomeWork();
    }
  
  	public void doSomeCheck() {
      infoService.getInfo();
    }
}

4. 相关拓展

几种事务失效的场景

上面说到的两个问题,其实就是@Transactional注解使用不当,导致失效的两种情形;除此之外,以下几种情况也会导致事务失效:

  • 业务代码中存在异常时,使用try…catch…语句块捕获,而catch语句块没有throw new RuntimeExecption异常;(最难被排查到问题且容易忽略)

  • 注解@TransactionalPropagation属性值设置错误即Propagation.NOT_SUPPORTED(一般不会设置此种传播机制)

  • mysql关系型数据库,且存储引擎是MyISAM而非InnoDB,则事务会不起作用(比较少见);

  • 业务代码抛出异常类型非RuntimeException,事务失效;Spring默认抛出未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

事务的传播行为

事务的传播行为也会影响到事务与事务之间的关系,一定要搞清楚,否则经常会出现很奇怪的问题。

具体来讲有以下几种属性:

  • propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:

  • Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )

  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

  • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

  • Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )

  • Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。

  • Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。

  • Propagation.NESTED :和 Propagation.REQUIRED 效果一样。

事务的隔离级别

SQL标准定义了4种事务隔离级别来避免3种数据不一致的问题。事务等级从高到低,分别为:

1.Serializable(序列化)

系统中所有的事务以串行地方式逐个执行,所以能避免所有数据不一致情况。

但是这种以排他方式来控制并发事务,串行化执行方式会导致事务排队,系统的并发量大幅下降,使用的时候要绝对慎重。

2.Repeatable read(可重复读)

一个事务一旦开始,事务过程中所读取的所有数据不允许被其他事务修改。

一个隔离级别没有办法解决“幻影读”的问题。

因为它只“保护”了它读取的数据不被修改,但是其他数据会被修改。如果其他数据被修改后恰好满足了当前事务的过滤条件(where语句),那么就会发生“幻影读”的情况。

其他两种事务隔离等级为:

3.Read Committed(已提交读)

一个事务能读取到其他事务提交过(Committed)的数据。

一个事务在处理过程中如果重复读取某一个数据,而且这个数据恰好被其他事务修改并提交了,那么当前重复读取数据的事务就会出现同一个数据前后不同的情况。

在这个隔离级别会发生“不可重复读”的场景。

4.Read Uncommitted(未提交读)

一个事务能读取到其他事务修改过,但是还没有提交的(Uncommitted)的数据。

数据被其他事务修改过,但还没有提交,就存在着回滚的可能性,这时候读取这些“未提交”数据的情况就是“脏读”。

在这个隔离级别会发生“脏读”场景。


参考:

标签:事务,不了,Transactional,千万,id,Propagation,方法,public
From: https://www.cnblogs.com/way2backend/p/17154817.html

相关文章

  • windows-win+快捷键用不了
    win10按win+e、win+r、win+d等win键无反应原因:win键盘被锁解决方式fn+win解锁win键即可(如果按过无反应,连按两次三次尝试即可)依旧无反应尝试fn+F2、fn+F6、fn+键盘......
  • linux-history没内容-上下箭头用不了
    history命令是被保存在内存中的,当退出或者登录shell时,会自动保存或读取。在内存中,历史命令仅能够存储1000条历史命令,该数量是由环境变量HISTSIZE进行控制。默认是不显示命......
  • Mysql、(五) 千万级数据批量插入
    @目录一、开启函数创建错误日志二、创建sql脚本1)创建函数2)创建存储过程3)调用存储过程一、开启函数创建错误日志查看是否开启:showvariableslike'%log_bin......
  • vue总结与整合(走过路过,千万不要错过,进来看看,全篇皆是精华)
    vue总结与整合vue简介 vue是一款用于构建用户界面的js框架,基于标准HTML、CSS和JavaScript构建,并提供了一套声明式的、组件化的编程模型,帮助开发者高效地开发用户界面。......
  • 虚拟机开启不了测试模式
    win+r,输入:gpedit.msc,打开本地组策略编辑器。用户配置、管理模块、系统、驱动程序安装、设备驱动程序的代码签名、上面选择:已启用,下面选择:忽略。计算机配置、管理模块、......
  • Java后端培训机构哪个比较好?不了解怎么选
    Java后端培训机构哪个比较好?这样的问题,对于机构的评价每个人都有自己的想法和看法,不能一概而论。所以,小编觉得,我们在看别人推荐的时候也要有自己的判断,通过实地考察在与自......
  • IDEA打开项目代码,识别不了资源文件(java文件、yml文件等),找不到或无法加载主类 com.xxxx
    如图所示,识别不了java文件和yml文件   点开EditConfigurations发现找不到启动类  运行会报错:  可以尝试如下解决办法办法:        然......
  • pycharm用不了pip
    环境:win10,安装有python2和python3,主要使用python3,所有环境变量配合的也是python3,之前一直用的好好的,pip可以在命令行和pycharm中正常使用.pycharm社区版,201802......
  • 第三方下载成功之后引用不了
    自学自动化的一天,问题记录(下载的包引入不了)  第三方库显示已经下载成功,引入的地方还是没有成功  然后重新新建一个项目引用就可以了,或者重启一下pycharm试下 ......
  • @Transactional注解的事务传播行为
    Transactional是Spring事务管理提供的注解,在一个方法中加上了这个注解,那么这个方法就将是有事务的,Transactional.propagation:传播行为1.Transactional注解中七种事务传......