Spring事务相关问题解决方案

有些spring相关的知识点之前一直没有仔细研究:比如spring的事务,并不是没有使用,也曾经简单的在某些需要事务处理的方法上通过增加事务注解来实现事务功能,仅仅是跟随使用(甚至并未测试过事务的正确性),至于如何在项目中配置事务,如何才能将事务写正确,事务的其它的一些原理性的东西从未花时间研究。最近同事正好抛出了一个问题,借此机会学习了一遍。

问题一:增加了readOnly=true的事务中包含写操作,为什么线上运行这段代码是正常的呢?

@Transactional(readOnly = true)
  public Integer getUID(String key, String type) {
    keyGeneratorDao.insert(key, type);
    keyGeneratorDao.update(key, type);
    return keyGeneratorDao.select(key, type);
  }

我为什么对这个问题感兴趣?

不懂这个readOnly参数的含义,之前写@Transactional的注解,那都是使用的默认值,不带显示参数。提出配置了readOnly参数后,理论上应该程序报错而实际上没有报错,想搞清楚为什么。

开始写单元测试:

在单元测试类中写事务函数以及测试方法

@Autowired
  private IkeyGeneratorDao keyGeneratorDao;

  @Transactional(readOnly=true)
  public Integer getId(String key, String type){
    return keyGeneratorDao.select(key, type);
  }
  @Transactional
  public Integer getUID(String key, String type) {
    keyGeneratorDao.insert(key, type);
    keyGeneratorDao.update(key, type);
    return this.getId(key,type);
  }
  @Test
  public void testCreateGuid(){
    int guid=this.getUID("12345", "jim");
    System.out.println(guid);
  }

测试结果显示正常,与上面提到的不允许进行写操作的观点相反,于是想起典型的事务生效问题。

挖的第一个坑:如果事务采用的是cglib动态代理,调用的方法与事务方法处在同一个类中事务不生效。

将两个事务事务转移到单独的类中,然后测试,类代码省略,只是将上面两个标记了@Transactional的方法封装在一个单独的类中。

@Autowired
  private KeyGeneratorService keyService;

  @Test
  public void testCreateGuid2(){
    int guid=this.keyService.getUID("12345", "jim");
    System.out.println(guid);
  }

测试结果显示也是正常,于是想确认下事务到底是否生效,加入异常以测试数据是否回滚,修改代码如下:

@Transactional
  public Integer getUID3(String key, String type) {
    keyGeneratorDao.insert(key, type);
    Integer.parseInt("aaa");//throw exception
    keyGeneratorDao.update(key, type);
}

测试结果显示事务回滚正常,可以排除事务环境配置问题。

挖的第二个坑:做测试一定要与原问题代码尽量保持一致,否则会产生其它的不明原因影响判断。通过对比原问题的代码发现我写的测试代码与问题代码有区别,readOnly是加在包含有写操作的方法上,而我的是两个方法,只有在读的方法上增加了readOnly,于时再次修改代码:

@Transactional(readOnly = true)
  public Integer getUID4(String key, String type) {
    keyGeneratorDao.insert(key, type);
    keyGeneratorDao.update(key, type);
    return keyGeneratorDao.select(key, type);
  }

测试结果显示运行不正常,提示如下错误:Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed,到这的确说明在在加了readOnly=true的事务内是不允许写入操作的。为什么这段代码在线上运行是成功的呢,于时查看前端的调用,发现前端调用的并不是直接标识了Transactional的方法,而是根据不同的具体业务重新包装的方法,比如我们需要生成订单的编号,前端只调用genOrderCode而不调用getUID。

非事务方法在内部调用了本类事务方法,然后非事务方法被外部调用ServicegenOrderCode,是一个非事务方法,内部调用了getUIDgetUID,是一个事务方法

@Autowired
  private IkeyGeneratorDao keyGeneratorDao;

  @Transactional(readOnly = true)
  public Integer getUID(String key, String type) {
    keyGeneratorDao.insert(key, type);
    keyGeneratorDao.update(key, type);
    return keyGeneratorDao.select(key, type);
  }

  public String genOrderCode(Date orderDate)
  {
    SimpleDateFormat df = new SimpleDateFormat("yyMMddHH");
    String ticketDate = df.format(orderDate);
    Integer uid = getUID(ticketDate, ORDER_CODE);    

    return ticketDate + genRandom2(uid.toString(), 3, 3) ;
  }

Test,外部类调用非事务方法

  @Test
  public void testCreateGuid3(){
    String guid=this.keyService.genOrderCode(new Date());
    System.out.println(guid);
  }

测试结果居然是正常的,这与线上运行的结果相同,后面经同事提醒,这又是一个不正确使用事务的案例。

挖的第三个坑:当调用一个类的非事务方法且这个非事务方法内部调用了本类自身的事务方法,那么事务也不会生效。

问题二:下面的代码可以实现事务回滚吗?

Service

  • genOrderCode方法调用
  • getUID2两个方法都是具备相同的事务参数
  • getUID2抛出异常
  • genOrderCode捕获这个异常
@Transactional
  public Integer getUID2(String key, String type) {
    keyGeneratorDao.insert(key, type);
    Integer.parseInt("aaa");//throw exception
    keyGeneratorDao.update(key, type);
    return keyGeneratorDao.select(key, type);
  }

  @Transactional
  public String genOrderCode(Date orderDate)
  {
    try{
      SimpleDateFormat df = new SimpleDateFormat("yyMMddHH");
      String ticketDate = df.format(orderDate);
      Integer uid = getUID2(ticketDate, ORDER_CODE);    

      return ticketDate + genRandom2(uid.toString(), 3, 3) ;
    }catch(Exception ex){
      System.out.println(ex);
    }
    return null;
  }

Test

  @Test
  public void testCreateGuid3(){
    String guid=this.keyService.genOrderCode(new Date());
    System.out.println(guid);
  }

执行测试代码,发现可以成功提交,但数据是不完整的,因为更新操作没有完成。为什么会是这样的呢?因为默认的Propagation.REQUIRED指明多个操作处于一个事务中,由于genOrderCode有异常处理,所以即使getUID2中发生异常,系统也会认定提交是合法的,因此会出现插入操作正常更新不正常但事务正常提交并不回滚的情况。
如果显示指定Propagation.REQUIRES_NEW呢?

@Transactional(propagation=Propagation.REQUIRES_NEW)
  public Integer getUID2(String key, String type) {
    keyGeneratorDao.insert(key, type);
    Integer.parseInt("aaa");//throw exception
    keyGeneratorDao.update(key, type);
    return keyGeneratorDao.select(key, type);
  }

再执行相同的测试,数据正常回滚,这里提供两张图,可以看的清楚些(因为常用的就这两种,其它的有兴趣可以多多研究)

REQUIRED

REQUIRES_NEW

通过事务的两个小问题,总结出解决问题的一些小技巧或者叫经验:发现问题之后,不要局限于某个点,最好根据上下文来结合分析,比如问题一的readonly可写入,单看那段代码很难找出合理的解释,只有结合前后端调用才能找出根本原因。写单元测试尽量写相同的代码,否则有可能会出现一些干扰项影响判断。学习呢,有时间尽量学的全点,比如@Transactional这个注解,除了readOnly还有Propagation等等。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Spring事务传播行为问题解决

    这篇文章主要介绍了Spring事务传播行为问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.简介 Spring事务配置及相关说明详见:https://www.jb51.net/article/177710.htm.这里说明spring事务的几点注意: 1.默认只会检查回滚RuntimeException的异常. 2.@Transactional注解只能作用于public的方法上,默认传播行为 Propagation.REQUIRED

  • SpringBoot内部调用事务不起作用问题的解决方案

    在做业务开发时,遇到了一个事务不起作用的问题.大概流程是这样的,方法内部的定时任务调用了一个带事务的方法,失败后事务没有回滚.查阅资料后,问题得到解决,记录下来分享给大家. 场景 我在这里模拟一个场景,大概的调用方式就如下面的代码这样. @Override @Transactional(rollbackFor = RuntimeException.class) public void insertUser(User user) { userMapper.insertUser(user); thr

  • Spring声明式事务和@Aspect的拦截顺序问题的解决

    在使用AbstractRoutingDataSource配置多数据源时,发现使用@aspect配置的DataSourceSwitchAspect总是在声明式事务之后执行,配置了Order依然不行,经过调研发现是由于两者的aop代理方式不一致导致. 在spring内部,是通过BeanPostProcessor(<spring 攻略>一书中翻译为,后处理器)来完成自动创建代理工作的.根据匹配规则的不同大致分为三种类别: 1.匹配Bean的名称自动创建匹配到的Bean的代理,实现类BeanNameA

  • Spring事务失效问题分析及解决方案

    这篇文章主要介绍了Spring事务失效问题分析及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 隔离级别 在 TransactionDefinition.java 接口中,定义了"四种"的隔离级别枚举: /** * [Spring 独有]使用后端数据库默认的隔离级别 * * MySQL 默认采用的 REPEATABLE_READ隔离级别 * Oracle 默认采用的 READ_COMMITTED隔离级别 */ int ISOL

  • Spring boot jpa 删除数据和事务管理的问题实例详解

    今天我们介绍的是jpa删除和事务的一些坑,接下来看看具体内容. 业务场景(这是一个在线考试系统)和代码:根据问题的id删除答案 repository层: int deleteByQuestionId(Integer questionId); service 层: public void deleteChoiceAnswerByQuestionId(Integer questionId) { choiceAnswerRepository.deleteByQuestionId(questionId)

  • 完美解决Spring声明式事务不回滚的问题

    疑问,确实像往常一样在service上添加了注解 @Transactional,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚.于是就对相关代码进行了一番测试,结果发现一下踩进了两个坑,确实是事务未回滚导致的数据不一致. 下面总结一下经验教训: Spring事务的管理操作方法 编程式的事务管理 实际应用中很少使用 通过使用TransactionTemplate 手动管理事务 声明式的事务管理 开发中推荐使用(代码侵入最少) Spring的声明式事

  • 解决spring mvc 多数据源切换,不支持事务控制的问题

    一个项目中需要使用两个数据库,Oracle 和Mysql,于是参考各个blog,实现此功能.写好后才发现,原来的事务失效了,我去... spring-mybatis.xml 配置 <bean id="configReader" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer"> <property name="location

  • 详解Spring Boot微服务如何集成fescar解决分布式事务问题

    什么是fescar? 关于fescar的详细介绍,请参阅fescar wiki. 传统的2PC提交协议,会持有一个全局性的锁,所有局部事务预提交成功后一起提交,或有一个局部事务预提交失败后一起回滚,最后释放全局锁.锁持有的时间较长,会对并发造成较大的影响,死锁的风险也较高. fescar的创新之处在于,每个局部事务执行完立即提交,释放本地锁:它会去解析你代码中的sql,从数据库中获得事务提交前的事务资源即数据,存放到undo_log中,全局事务协调器在回滚的时候直接使用undo_log中的数据覆

  • Spring事务相关问题解决方案

    有些spring相关的知识点之前一直没有仔细研究:比如spring的事务,并不是没有使用,也曾经简单的在某些需要事务处理的方法上通过增加事务注解来实现事务功能,仅仅是跟随使用(甚至并未测试过事务的正确性),至于如何在项目中配置事务,如何才能将事务写正确,事务的其它的一些原理性的东西从未花时间研究.最近同事正好抛出了一个问题,借此机会学习了一遍. 问题一:增加了readOnly=true的事务中包含写操作,为什么线上运行这段代码是正常的呢? @Transactional(readOnly = tr

  • Spring事务原理解析

    目录 前言 问题描述 代码复现 排查 1. 锁失效 2. 事务隔离级别 3. 修改Spring事务传播配置 解决方案 前言 最近在编写公司APP产品的商品砍价功能,其中有一个接口涉及并发访问.自测时通过ApiFox接口管理工具进行压测,落地数据时出现了"锁失效"的情景.十分感谢后端小伙伴的帮助排查,解决了这个问题. 问题描述 并发接口中,先对主表数据进行读取,进行业务判断后,新增.修改它表的数据.在理应串行执行的情况下发生了多个请求线程读取到了相同的主表数据,导致数据处理异常.也正是前

  • Spring事务失效场景原理及解决方案

    1.事务失效-自身调用(通过REQUIRES.REQUIRES_NEW传播属性):自身调用即调该类自己的方法. 同类OrderServiceImpl 中 doSomeThing()方法 不存在事务,该方法去调用本类中的存在事务注解的 insertAndUpdateOrderInfo() 方法.但是insertAndUpdateOrderInfo() 其实是无法保证预想的事务性. 示列验证: OrderServiceImpl.insertAndUpdateOrderInfo方法中upateData

  • spring事务异常回滚实例解析

    最近遇到了事务不回滚的情况,我还考虑说JPA的事务有bug?我想多了....... 为了打印清楚日志,很多方法我都加tyrcatch,在catch中打印日志.但是这边情况来了,当这个方法异常时候日志是打印了,但是加的事务却没有回滚. 例: 类似这样的方法不会回滚(一个方法出错,另一个方法不会回滚): if(userSave){ try { userDao.save(user); userCapabilityQuotaDao.save(capabilityQuota); } catch (Exce

  • Spring事务管理原理及方法详解

    这篇文章主要介绍了Spring事务管理原理及方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 事务,在日常开发或者面试中都必定会涉及到.开发工作中,结合数据库开发理解就是:一组dml要么全部成功执行提交,要么因为某一个操作异常,撤销之前所做的成功的操作,整体执行失败.再简单点的一句话:生死与共. 由此,可以看出,事务的必要性:在开发工作中,保证操作数据的安全性.事务的控制也就是保证数据的访问安全性. 一.事务的四大特性 A:原子性(ato

  • 8个Spring事务失效场景详解

    目录 前言 Spring事务原理 Spring事务失效场景 1. 抛出检查异常 2. 业务方法本身捕获了异常 3. 同一类中的方法调用 4. 方法使用 final 或 static关键字 5. 方法不是public 6. 错误使用传播机制 7. 没有被Spring管理 8. 多线程 总结 前言 作为Java开发工程师,相信大家对Spring种事务的使用并不陌生.但是你可能只是停留在基础的使用层面上,在遇到一些比较特殊的场景,事务可能没有生效,直接在生产上暴露了,这可能就会导致比较严重的生产事故.

  • 深入理解Spring事务原理

    一.事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的.对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:  1.获取连接 Connection con = DriverManager.getConnection()  2.开启事务con.setAutoCommit(true/false);  3.执行CRUD  4.提交事务/回滚事务 con.commit() / con.rollback();  5.关闭连接

  • Spring 事务隔离与事务传播的详解与对比

    Spring 事务隔离与事务传播的详解与对比 Spring是SSH中的管理员,负责管理其它框架,协调各个部分的工作.今天一起学习一下Spring的事务管理.Spring的事务管理分为声明式跟编程式.声明式就是在Spring的配置文件中进行相关配置:编程式就是用注解的方式写到代码里. Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource.TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分. DataSource. Tr

  • 学习spring事务与消息队列

    在开发过程中,遇到一个bug,产生bug的原因是spring事务提交晚于消息队列的生产消息,导致消息队列消费消息时获取到的数据不正确.这篇文章介绍问题的产生和一步步的解决过程. 一.问题的产生: 场景还原:接口中的一个方法,首先修改订单状态,然后向消息队列中生产消息,消息队列的消费者获取到消息检测订单状态,发现订单状态未更改. 代码: @Service(orderApi) public class OrderApiImpl implements OrderApi { @Resource MqSe

随机推荐