广告位联系
返回顶部
分享到

Spring事务相关问题解决方案的介绍

java 来源:未知 作者:酷站 发布时间:2020-02-27 15:54:39 人浏览
摘要

有些spring相关的知识点之前一直没有仔细研究:比如spring的事务,并不是没有使用,也曾经简单的在某些需要事务处理的方法上通过增加事务注解来实现事务功能,仅仅是跟随使用(甚至并未测试过事务的正确性),至于如何在项目中配置事务,如何才能将事务写正确

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

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

?
1
2
3
4
5
6
@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参数后,理论上应该程序报错而实际上没有报错,想搞清楚为什么。

开始写单元测试:

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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的方法封装在一个单独的类中。

?
1
2
3
4
5
6
7
8
@Autowired
  private KeyGeneratorService keyService;
 
  @Test
  public void testCreateGuid2(){
    int guid=this.keyService.getUID("12345", "jim");
    System.out.println(guid);
  }

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

?
1
2
3
4
5
6
@Transactional
  public Integer getUID3(String key, String type) {
    keyGeneratorDao.insert(key, type);
    Integer.parseInt("aaa");//throw exception
    keyGeneratorDao.update(key, type);
}

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

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

?
1
2
3
4
5
6
@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,是一个事务方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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,外部类调用非事务方法

?
1
2
3
4
5
@Test
public void testCreateGuid3(){
  String guid=this.keyService.genOrderCode(new Date());
  System.out.println(guid);
}

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

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

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

Service

  • genOrderCode方法调用
  • getUID2两个方法都是具备相同的事务参数
  • getUID2抛出异常
  • genOrderCode捕获这个异常
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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

?
1
2
3
4
5
@Test
public void testCreateGuid3(){
  String guid=this.keyService.genOrderCode(new Date());
  System.out.println(guid);
}

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

?
1
2
3
4
5
6
7
@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等等。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://www.cnblogs.com/ASPNET2008/p/5570237.html
相关文章
  • SpringBoot自定义错误处理逻辑介绍

    SpringBoot自定义错误处理逻辑介绍
    1. 自定义错误页面 将自定义错误页面放在 templates 的 error 文件夹下,SpringBoot 精确匹配错误信息,使用 4xx.html 或者 5xx.html 页面可以打印错误
  • Java实现手写一个线程池的代码

    Java实现手写一个线程池的代码
    线程池技术想必大家都不陌生把,相信在平时的工作中没有少用,而且这也是面试频率非常高的一个知识点,那么大家知道它的实现原理和
  • Java实现断点续传功能的代码

    Java实现断点续传功能的代码
    题目实现:网络资源的断点续传功能。 二、解题思路 获取要下载的资源网址 显示网络资源的大小 上次读取到的字节位置以及未读取的字节
  • 你可知HashMap为什么是线程不安全的
    HashMap 的线程不安全 HashMap 的线程不安全主要体现在下面两个方面 在 jdk 1.7 中,当并发执行扩容操作时会造成环形链和数据丢失的情况 在
  • ArrayList的动态扩容机制的介绍

    ArrayList的动态扩容机制的介绍
    对于 ArrayList 的动态扩容机制想必大家都听说过,之前的文章中也谈到过,不过由于时间久远,早已忘却。 所以利用这篇文章做做笔记,加
  • JVM基础之字节码的增强技术介绍

    JVM基础之字节码的增强技术介绍
    字节码增强技术 在上文中,着重介绍了字节码的结构,这为我们了解字节码增强技术的实现打下了基础。字节码增强技术就是一类对现有字
  • Java中的字节码增强技术

    Java中的字节码增强技术
    1.字节码增强技术 字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术。 参考地址 2.常见技术 技术分类 类
  • Redis BloomFilter布隆过滤器原理与实现

    Redis BloomFilter布隆过滤器原理与实现
    Bloom Filter 概念 布隆过滤器(英语:Bloom Filter)是1970年由一个叫布隆的小伙子提出的。它实际上是一个很长的二进制向量和一系列随机映射
  • Java C++算法题解leetcode801使序列递增的最小交换次

    Java C++算法题解leetcode801使序列递增的最小交换次
    题目要求 思路:状态机DP 实现一:状态机 Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int minSwap(int[] nums1, int[] nums2) { int n
  • Mybatis结果集映射与生命周期介绍

    Mybatis结果集映射与生命周期介绍
    一、ResultMap结果集映射 1、设计思想 对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了 2、resultMap的应用场
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计