这篇文章以一个问题开始,如果你知道答案的话就可以跳过不看啦@(o・ェ・)@

Q:在一个批量任务执行的过程中,调用多个子任务时,如果有一些子任务发生异常,只是回滚那些出现异常的任务,而不是整个批量任务,请问在Spring中事务需要如何配置才能实现这一功能呢?

隔离级别

隔离性(Isolation)作为事务特性的一个关键特性,它要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,在数据库层面都是使用锁来实现。

事务的隔离级别从低到高有以下四种:

  • READ UNCOMMITTED(未提交读):这是最低的隔离级别,其含义是允许一个事务读取另外一个事务没有提交的数据。READ UNCOMMITTED是一种危险的隔离级别,在实际开发中基本不会使用,主要是由于它会带来脏读问题。

    时间 事务1 事务2 备注
    1 读取库存为100 --- ---
    2 扣减库层50 --- 剩余50
    3 扣减库层50 ---
    4 提交事务 库存保存为0
    5 回滚事务 --- 事务回滚为0

脏读对于要求数据一致性的应用来说是致命的,目前主流的数据库的隔离级别都不会设置成READ UNCOMMITTED。不过脏读虽然看起来毫无用处,但是它主要优点是并发能力高,适合那些对数据一致性没有要求而追求高并发的场景。

  • READ COMMITTED(读写提交): 它是指一个事务只能读取另外一个事务已经提交的数据,不能读取未提交的数据。READ COMMITTED会带来不可重复读的问题:
时间 事务1 事务2 备注
1 读取库存为1
2 扣减库存 事务未提交
3 读取库存为1
4 提交事务 库存变成0
5 扣减库存 库存为0,无法扣减

不可重复读和脏读的区别是:脏读读取到的是未提交的数据,而不可重复读读到的确实已经提交的数据,但是违反了数据库事务一致性的要求。

一般来说,不可重复读的问题是可以接受的,因为其读到的是已经提交的数据,本身并不会带来很大的问题。因此,很多数据库如(ORACLE,SQL SERVER)将其默认隔离级别设置为READ COMMITTED,允许不可重复读的现象。

  • REPEATABLE READ (可重复读):可重复读的目标是为了克服READ COMMITED中出现的不可重复读,它指在同一个事务内的查询都是与事务开始时刻一致,以上表为例,在REPEATABLE READ隔离级别下它会发生如下变化:
时间 事务1 事务2 备注
1 读取库存为1
2 扣减库存 事务未提交
3 读取库存 不允许读取,等待事务1提交
4 提交事务 库存变成0
5 读取库存 库存为0,无法扣减

REPEATABLE READ虽然解决了不可重复读问题,但是他又会带来幻读问题,幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。

时间 事务A 事务2 备注
1 begin begin
2 读取id为100的数据 没有数据
3 插入id为100的数据
4 提交事务
5 读取id为100的数据 没有数据
6 更新id为100的数据 成功
7 读取id为100的数据 读取成功
8 提交事务

事务B在第2步第一次读取id=99的记录时,读到的记录为空,说明不存在id=99的记录。随后,事务A在第3步插入了一条id=99的记录并提交。事务B在第5步再次读取id=99的记录时,读到的记录仍然为空,但是,事务B在第6步试图更新这条不存在的记录时,竟然成功了,并且,事务B在第8步再次读取id=99的记录时,记录出现了。

  • SERIALIZABLE(串行化):数据库最高的隔离级别,它要求所有的SQL都会按照顺序执行,这样可以克服上述所有隔离出现的各种问题,能够完全包住数据的一致性。

Spring中配置隔离级别

在Spring项目中配置隔离级别只需要做如下操作

  1. @Transactional(isolation = Isolation.SERIALIZABLE)
  2. public int insertUser(User user){
  3. return userDao.insertUser(user);
  4. }

上面的代码中我们使用了串行化的隔离级别来包住数据的一致性,这使它将阻塞其他的事务进行并发,所以它只能运用在那些低并发而又需要保证数据一致性的场景下。

隔离级别字典:

  1. DEFAULT(-1), ## 数据库默认级别
  2. READ_UNCOMMITTED(1),
  3. READ_COMMITTED(2),
  4. REPEATABLE_READ(4),
  5. SERIALIZABLE(8);

传播行为

在Spring中,当一个方法调用另外一个方法时,可以让事务采取不同的策略工作,如新建事务或者挂起当前事务等,这便是事务的传播行为。

定义

在Spring的事务机制中对数据库存在7种传播行为,通过枚举类Propagation定义。

  1. public enum Propagation {
  2. /**
  3. * 需要事务,默认传播性行为。
  4. * 如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法
  5. */
  6. REQUIRED(0),
  7. /**
  8. * 支持事务,如果当前存在事务,就沿用当前事务,
  9. * 如果不存在,则继续采用无事务的方式运行子方法
  10. */
  11. SUPPORTS(1),
  12. /**
  13. * 必须使用事务,如果当前没有事务,抛出异常
  14. * 如果存在当前事务,就沿用当前事务
  15. */
  16. MANDATORY(2),
  17. /**
  18. * 无论当前事务是否存在,都会创建新事务允许方法
  19. * 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
  20. */
  21. REQUIRES_NEW(3),
  22. /**
  23. * 不支持事务,当前存在事务时,将挂起事务,运行方法
  24. */
  25. NOT_SUPPORTED(4),
  26. /**
  27. * 不支持事务,如果当前方法存在事务,将抛出异常,否则继续使用无事务机制运行
  28. */
  29. NEVER(5),
  30. /**
  31. * 在当前方法调用子方法时,如果子方法发生异常
  32. * 只回滚子方法执行过的SQL,而不回滚当前方法的事务
  33. */
  34. NESTED(6);
  35. ......
  36. }

日常开发中基本只会使用到REQUIRED(0),REQUIRES_NEW(3),NESTED(6)三种。

NESTEDREQUIRES_NEW是有区别的。NESTED传播行为会沿用当前事务的隔离级别和锁等特性,而REQUIRES_NEW则可以拥有自己独立的隔离级别和锁等特性。

NESTED的实现主要依赖于数据库的保存点(SAVEPOINT)技术,SAVEPOINT记录了一个保存点,可以通过ROLLBACK TO SAVEPOINT来回滚到某个保存点。如果数据库支持保存点技术时就启用保存点技术;如果不支持就会新建一个事务去执行代码,也就相当于REQUIRES_NEW

Transactional自调用失效

如果一个类中自身方法的调用,我们称之为自调用。如一个订单业务实现类OrderServiceImpl中有methodA方法调用了自身类的methodB方法就是自调用,如:

  1. @Transactional
  2. public void methodA(){
  3. for (int i = 0; i < 10; i++) {
  4. methodB();
  5. }
  6. }
  7. @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
  8. public int methodB(){
  9. ......
  10. }

在上面方法中不管methodB如何设置隔离级别和传播行为都是不生效的。即自调用失效。

这主要是由于@Transactional的底层实现原理是基于AOP实现,而AOP的原理是动态代理,在自调用的过程中是类自身的调用,而不是代理对象去调用,那么就不会产生AOP,于是就发生了自调用失败的现象。

要克服这个问题,有2种方法:

  • 编写两个Service,用一个Service的methodA去调用另外一个Service的methodB方法,这样就是代理对象的调用,不会有问题;
  • 在同一个Service中,methodA不直接调用methodB,而是先从Spring IOC容器中重新获取代理对象`OrderServiceImpl·,获取到后再去调用methodB。说起来有点乱,还是show you the code。
  1. public class OrderServiceImpl implements OrderService,ApplicationContextAware {
  2. private ApplicationContext applicationContext = null;
  3. @Override
  4. public void setApplicationContext(ApplicationContext applicationContext) {
  5. this.applicationContext = applicationContext;
  6. }
  7. @Transactional
  8. public void methodA(){
  9. OrderService orderService = applicationContext.getBean(OrderService.class);
  10. for (int i = 0; i < 10; i++) {
  11. orderService.methodB();
  12. }
  13. }
  14. @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
  15. public int methodB(){
  16. ......
  17. }
  18. }

上面代码中我们先实现了ApplicationContextAware接口,然后通过applicationContext.getBean()获取了OrderService的接口对象。这个时候获取到的是一个代理对象,也就能正常使用AOP的动态代理了。

回到最开始的那个问题,看完这篇文章是不是有答案了呢?

浅谈Spring的事务隔离级别与传播性的更多相关文章

  1. 浅谈MySQL的事务隔离级别

    希望这篇文章能够阐述清楚跟数据库相关的四个概念:事务.数据库读现象.隔离级别.锁机制 一.事务 先来看下百度百科对数据库事务的定义: 作为单个逻辑单元执行一系列操作,要么完全执行,要么完全不执行.事务 ...

  2. Spring事务隔离级别和传播性

    事务的隔离级别也分为四种: read uncommited(读未提交). read commited(读提交). read repeatable(读重复). serializable(序列化), 这四 ...

  3. Spring事务隔离级别和传播特性

    相信每个人都被问过无数次Spring声明式事务的隔离级别和传播机制吧!今天我也来说说这两个东西. 加入一个小插曲, 一天电话里有人问我声明式事务隔离级别有哪几种, 我就回答了7种, 他问我Spring ...

  4. Spring随笔 - 事务隔离级别

    Spring提供5中事务隔离级别: ISOLATION_DEFAULT:使用数据库后端的默认隔离级别. ISOLATION_READ_UNCOMMITTED:允许读取尚未提交的数据变更.可能会导致脏读 ...

  5. 事务隔离级别与传播机制,spring+mybatis+atomikos实现分布式事务管理

    1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). 原子性(Atomicity):即事务是不可分割的最小工作单 ...

  6. Spring事务隔离级别与传播机制详解,spring+mybatis+atomikos实现分布式事务管理

    原创说明:本文为本人原创作品,绝非他处转载,转账请注明出处 1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). ...

  7. Spring声明式事务@Transactional 详解,事务隔离级别和传播行为

    @Transactional注解支持9个属性的设置,这里只讲解其中使用较多的三个属性:readOnly.propagation.isolation.其中propagation属性用来枚举事务的传播行为 ...

  8. Spring事务隔离级别和传播特性(转)

    相信每个人都被问过无数次Spring声明式事务的隔离级别和传播机制吧!今天我也来说说这两个东西. 加入一个小插曲,一天电话里有人问我声明式事务隔离级别有哪几种,我就回答了7种,他问我Spring的版本 ...

  9. spring事务隔离级别、传播机制以及简单配置

    转自 https://blog.csdn.net/zht741322694/article/details/78676964 一.spring支持的事务声明方式1.  编程式事务  当系统需要明确的, ...

随机推荐

  1. 正则表达式-Regex详解

    1.什么是正则表达式 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符.及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑.给定一个正 ...

  2. 【第一篇】spring boot 快速入门

    1.开发环境 开发工具:IDEA2018.2.1 JDK:1.9 Maven : 3.3.9 操作系统:window 7 / window 10 2.项目结构 3.详细步骤 3.1 使用IDEA新建M ...

  3. Python 命令行之旅:使用 argparse 实现 git 命令

    作者:HelloGitHub-Prodesire HelloGitHub 的<讲解开源项目>系列,项目地址:https://github.com/HelloGitHub-Team/Arti ...

  4. HDU4578 线段树(区间更新 + 多种操作)和平方,立方

    参考:https://www.cnblogs.com/H-Vking/p/4297973.html 题意: 虽然是比较裸的线段树,但是比较麻烦,并且有很多细节需要考虑,对着别人的ac代码debug了一 ...

  5. CF1027D Mouse Hunt 思维

    Mouse Hunt time limit per test 2 seconds memory limit per test 256 megabytes input standard input ou ...

  6. c++11特性学习总结

    ubuntu 16.04 自带gcc 5.4 支持c++11 ubuntu 18.04 自带gcc 7.3 支持c++14 查看编译器支持: c++11 c++14 c++17 c++11 featu ...

  7. VS Code 前端开发常用快捷键插件

    一.vs code 的常用快捷键 1.注释:a) 单行注释:[ctrl+k,ctrl+c] 或 ctrl+/ b) 取消单行注释:[ctrl+k,ctrl+u] (按下ctrl不放,再按k + u) ...

  8. 15 个有用的 MySQL/MariaDB 性能调整和优化技巧

    MySQL 是一个强大的开源关系数据库管理系统(简称 RDBMS).它发布于 1995 年(20年前).它采用结构化查询语言(SQL),这可能是数据库内容管理中最流行的选择.最新的 MySQL 版本是 ...

  9. Numpy版本问题,import tensorflow as tf 报警:“ FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'”

    tensorflow成功安装后 import tensorflow as tf 报警:“ FutureWarning: Passing (type, 1) or '1type' as a synony ...

  10. IDEA中运行测试方法

    1. 2. 3. 4. 5.