有些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. Android后台保活实践总结:即时通讯应用无法根治的“顽疾”

    前言 Android进程和Service的保活,是困扰Android开发人员的一大顽疾.因涉及到省电和内存管理策略,各厂商基于自家的理解,在自已ROOM发布于都对标准Android发行版作为或多或少的 ...

  2. viewpage listview gridview加载本地大图多图OOM处理办法

    很少上博客园写东西了. 最近在写公司项目,由于需要加载本地相册通过viewpager方式来加载, 最后发现直接进入界面就OOM了. 经过几天的整理最终搞定. 现在将加载本地和加载网络图片的缓存工具类贴 ...

  3. KnockoutJS 3.X API 第四章 数据绑定(1) 文本及样式绑定

    目录 本节将介绍六种文本绑定方式: visible绑定 text绑定 html绑定 css绑定 style绑定 attr绑定 可见文本绑定(visible) 使用visible绑定,来控制DOM元素的 ...

  4. js高程读书笔记(1-3章)

    一.js简介 js是一种专为与网页交互而设计的脚本语言,由以下三个不同的部分组成: 1.ECMAScript,由ECMA-262(它规定了语言的这些组成部分:语法,类型,语句,关键字,保留字,操作符, ...

  5. Mina、Netty、Twisted一起学(七):发布/订阅(Publish/Subscribe)

    消息传递有很多种方式,请求/响应(Request/Reply)是最常用的.在前面的博文的例子中,很多都是采用请求/响应的方式,当服务器接收到消息后,会立即write回写一条消息到客户端.HTTP协议也 ...

  6. (转)rlwrap真是一个好东西

    在Linux下面使用sqlplus很不爽,上下键,退格键都不能用,严重降低生产效率. 某一天终于发现了这个rlwrap这个好东西,特写此文记录. 下载地址如下: http://utopia.knowa ...

  7. Spring MVC 学习总结(三)——请求处理方法Action详解

    Spring MVC中每个控制器中可以定义多个请求处理方法,我们把这种请求处理方法简称为Action,每个请求处理方法可以有多个不同的参数,以及一个多种类型的返回结果. 一.Action参数类型 如果 ...

  8. WebStorm 9 自动编译 SCSS 产出 CSS 和 source maps

    1.  上一节我们学习了Windows下搭建Ruby开发环境,也为这一节的学习做了铺垫.因为本节需要在Ruby环境下安装SASS.详细请见:http://www.cnblogs.com/wind128 ...

  9. apache解析多个域名

    之前搭建了一个网站在这台服务器上,今天心血来潮准备搭建个word press 博客,准备使用二级域名 blog.xdlxb.cn 来解析. 只需要设置httpd.conf 文件就可以了 如下 开启重定 ...

  10. Android正则表达式使用及性能隐患分析

    场景:找出一个关键词在一条短信中出现的次数 使用正则的实现方式: public static int findKeyWordCount(String srcText, String keyword) ...