有些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。

  • 非事务方法在内部调用了本类事务方法,然后非事务方法被外部调用

    • Service

      • genOrderCode,是一个非事务方法,内部调用了getUID
      • getUID,是一个事务方法
    @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等等。

两个与spring事务相关的问题的更多相关文章

  1. Spring 事务相关点整理

    Spring和事务的关系 关系型数据库.某些消息队列等产品或中间件称为事务性资源,因为它们本身支持事务,也能够处理事务. Spring很显然不是事务性资源,但是它可以管理事务性资源,所以Spring和 ...

  2. Spring 事务相关

    事务类型 数据库事务类型有本地事务和分布式事务: 本地事务:就是普通事务,能保证单台数据库上的操作的ACID,被限定在一台数据库上: 分布式事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据 ...

  3. spring事务相关

    在 SPRING 中一共定义了六种事务传播属性 PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务.这是最常见的选择. PROPAGATION_SUPPOR ...

  4. Spring学习--Spring事务相关速记

    数据库事务 事务特性: 原子性,事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做 一致性,在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是处于正确的状态 隔离性, ...

  5. 一文带你认识Spring事务

    前言 只有光头才能变强. 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y Spring事务管理我相信大家都用得很多,但可能仅仅 ...

  6. Spring事务实现分析

    一.Spring声明式事务用法 1.在spring配置文件中配置事务管理器 <bean id="baseDataSource" class="com.alibaba ...

  7. Spring事务用法示例与实现原理

    关于Java中的事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性,一致性,隔离性和持久性.对于Spring事务,其最终还是在数据库层面实现的,而Spring只是以一种比 ...

  8. 死磕Spring之AOP篇 - Spring 事务详解

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...

  9. spring事务管理及相关知识

    最近在项目中遇到了spring事务的注解及相关知识,突然间感觉自己对于这部分知识只停留在表面的理解层次上,于是乎花些时间上网搜索了一些文章,以及对于源码的解读,整理如下: 一.既然谈到事务,那就先搞清 ...

随机推荐

  1. 对于System.Net.Http的学习(三)——使用 HttpClient 检索与获取过程数据

    对于System.Net.Http的学习(一)——System.Net.Http 简介 对于System.Net.Http的学习(二)——使用 HttpClient 进行连接 如何使用 HttpCli ...

  2. SQLSERVER中的ALLOCATION SCAN和RANGE SCAN

    SQLSERVER中的ALLOCATION SCAN和RANGE SCAN 写这篇文章的开始,我还不知道ALLOCATION SCAN的工作原理是怎样的,网上资料少得可怜 求助了园子里的某位大侠,他看 ...

  3. RAC Concept

    1. RAC的高可用性 RAC的高可用性主要包含以下几点: 1> 实现节点间的负载均衡. 2> 实现失败切换的功能. 3> 通过Service组件来控制客户端的访问路径. 4> ...

  4. MySQL的学习--join和union的用法

    感觉工作之后一直在用框架,数据库的一些基本的东西都忘记了,这次借着这个系列的博客回顾一下旧知识,学一点新知识. 今天就先从join和union开始. join 是两张表做交连后里面条件相同的部分记录产 ...

  5. 使用Ambari安装hadoop集群

    最近需要做些spark的工作,所以弄了几台dell7500就这么准备开始搭建集群,之前用过几台更破的台式机搭建过一次,折腾了半个月之久,终于成功搭建,这次不想走老路,所以网上查了一下,发现一个神器AM ...

  6. c#基础之长度可变类型相同的参数列表

    为了简化编码,c#提供了一个特殊的关键字params,允许在调用方法时提供数量可变的实参,而不是由方法实现固定好的形参数量.先看代码吧. using System; using System.Linq ...

  7. Docker - Docker国内镜像的配置及使用

    Docker国内镜像 DaoCloud - Docker加速器 阿里云 - 开发者平台 微镜像 - 希云cSphere 镜像广场 - 时速云 灵雀云 网易蜂巢 阿里云的Docker加速器 阿里云 - ...

  8. inuit.css – 基于 Sass 的强大,可扩展的 CSS 框架

    inuit.css 是一个强大的,可扩展的 CSS 框架,另外还是基于 Sass,面向对象的框架.inuit.css 是建立在 BEM 风格的命名约定,非常适合于想要专注于创意而不是代码的设计师以及喜 ...

  9. android调试输出

    测试时不想直接debug总要调试输出一些字符串信息,那以下方法可选: 1.用Log.i(TAG, "onCreate");日志输出. 先要引用 import android.uti ...

  10. 3.创建第一个android项目

    安卓开发学习笔记 1.安卓开发之环境搭建 2.SDK目录结构和adb工具及命令介绍 3.创建第一个android项目 1.打开Eclipse,选择File——>new——>others.. ...