• 1. Spring 中事务的实现
    • 1.1 Spring 中编程式事务的实现
    • 1.2 Spring 中生命式的事务(自动事务)实现
      • 1.2.1 @Transactional 作用范围
      • 1.2.2 @Transactional 参数说明
      • 1.2.3 @Transactional 在异常被捕获的情况下,不会进⾏事务⾃动回滚
        • 1.2.3.1 解决方案 1: 将异常重新抛出
        • 1.2.3.2 解决方案 2: 使用代码方式手动回滚当前事务
  • 2. 事务隔离级别
    • 2.1 事务特性回顾
    • 2.2 Spring 中设置事务隔离级别
      • 2.2.1 MySQL 事务隔离级别有 4 种
      • 2.2.2 Spring 事务隔离级别有 5 种
  • 3. Spring 事务传播机制
    • 3.1 事务传播机制有哪些?
    • 3.2 Spring 事务传播机制使⽤和各种场景演示
      • 3.2.1 ⽀持当前事务(REQUIRED)
      • 3.2.2 不⽀持当前事务(REQUIRES_NEW)
      • 3.2.3 不支持当前事务 (NOT_SUPPORTED)
      • 3.2.4 嵌套事务 ( NESTED)
      • 3.2.5 加入事务 (REQUIRED)

1. Spring 中事务的实现

1.1 Spring 中编程式事务的实现

包含了 3 个重要的操作:

  1. 获取事务(开启事务)
  2. 提交事务
  3. 回滚事务

依赖 2 个重要的对象:
4. DataSourceTransactionManager(⽤来获取事务(开启事务)、提交或回滚事务的)
5. TransactionDefinition (是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus)

application.yml

# 配置数据库的连接字符串
spring:datasource:url: jdbc:mysql://127.0.0.1/mycnblog?characterEncoding=utf8username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:mapper-locations: classpath:mapper/**Mapper.xmlconfiguration: # 配置打印 MyBatis 执行的 SQLlog-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置打印 MyBatis 执行的 SQL
logging:level:com:example:demo: debug

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper"><insert id="add">insert into userinfo(username,password) values(#{username},#{password})</insert>
</mapper>

UserInfo

import lombok.Data;@Data
public class UserInfo {private int id;private String username;private String password;private String photo;private String createtime;private String updatetime;private int state;
}

Userservice

import com.example.demo.mapper.UserMapper;
import com.example.demo.model.UserInfo;
import org.springframework.stereotype.Service;import javax.annotation.Resource;@Service
public class UserService {@Resourceprivate UserMapper userMapper;public int add(UserInfo userInfo) {return userMapper.add(userInfo);}
}

UserMappper

import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper {int add(UserInfo userInfo);
}

UserController

import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class UserController {@Autowiredprivate UserService userService;// JDBC 事务管理器@Autowiredprivate DataSourceTransactionManager transactionManager;// 定义事务属性@Autowiredprivate TransactionDefinition transactionDefinition;// 在此方法中使用编程式的事务@RequestMapping("/add")public int add(UserInfo userInfo){//非空校验(验证用户名和密码不能为空)if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())){return 0;}// 开启事务(获取事务)TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);int result = userService.add(userInfo);System.out.println("add 受影响的行数: "+result);// 提交事务/回滚事务transactionManager.rollback(transactionStatus); //回滚事务return result;}}

再看我们数据库并没有添加进来:

想要提交的话:

1.2 Spring 中生命式的事务(自动事务)实现

声明式事务的实现很简单,只需要在需要的⽅法上添加 @Transactional 注解就可以实现了,⽆需⼿动开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了没有处理的异常会⾃动回滚事务

// 使用声明式事务@Transactional // 在方法之前,自动开启事务,在方法执行完之后,自动提交事务,如果出现异常,自动回滚事务@RequestMapping("/add2")public int add2(UserInfo userInfo) {// 非空效验【验证用户名和密码不为空】if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())) return 0;int result = userService.add(userInfo);System.out.println("add 受影响的行数:" + result);int num = 10 / 0;return result;}

已经添加了:

再查数据库并没有添加:

注释掉异常:

再重启就可以添加进来了:

1.2.1 @Transactional 作用范围

@Transactional 可以⽤来修饰⽅法或类:
修饰⽅法时:需要注意只能应⽤到 public ⽅法上,否则不⽣效。推荐此种⽤法。
修饰类时:表明该注解对该类中所有的 public ⽅法都⽣效(都会自动的开启和提交(回滚)事务)。

1.2.2 @Transactional 参数说明

参数 作用
value 当配置了多个事务管理器时, 可以使用该属性指定选择哪个事务管理器
transactionManager 当配置了多个事务管理器时, 可以使用该属性指定选择哪个事务管理器.
propagation 事务的传播行为. 默认为 Propagation.REQUIRED
isolation 事务的隔离级别. 默认为 Isolation.DAEFAULT
timeout 事务的超时事件. 默认值为-1, 如果超过该时间限制但事务还没有完成, 则自动回滚事务.
readOnly 指定事务是否只读事务. 默认为 false. 为了忽略那些不需要事务的方法, 比如读取数据, 可以设置为 read-only 为 true
rollbackFor 用于指定能够触发事务回滚的异常类型, 可以指定多个异常类型
rollbackForClassName 用于指定能够触发事务回滚的异常类型, 可以指定多个异常类型
noRollbackFor 抛出指定的异常类型, 不回滚事务, 也可以指定多个异常类型
noRollbackForClassName 抛出指定的异常类型, 不回滚事务, 也可以指定多个异常类型

1.2.3 @Transactional 在异常被捕获的情况下,不会进⾏事务⾃动回滚

// 使用声明式事务@Transactional // 在方法之前,自动开启事务,在方法执行完之后,自动提交事务,如果出现异常,自动回滚事务@RequestMapping("/add3")public int add3(UserInfo userInfo) {// 非空效验【验证用户名和密码不为空】if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())) return 0;int result = userService.add(userInfo);System.out.println("add 受影响的行数:" + result);try {int num = 10 / 0;} catch (Exception e) {e.printStackTrace();}return result;}

此时启动程序不会报错:

再看数据库并没有进行回滚:

1.2.3.1 解决方案 1: 将异常重新抛出

先删除之前的数据:

启动项目进行测试:

此时并没有成功插入:

1.2.3.2 解决方案 2: 使用代码方式手动回滚当前事务

并没有插入进来:

2. 事务隔离级别

2.1 事务特性回顾

事务有4 ⼤特性(ACID),原⼦性、持久性、⼀致性和隔离性,具体概念如下:

原⼦性(Atomicity,或称不可分割性)
⼀致性(Consistency)
隔离性(Isolation,⼜称独⽴性)
持久性(Durability)

原⼦性: ⼀个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执⾏过程中发⽣错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执⾏过⼀样。

⼀致性: 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写⼊的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以⾃发性地完成预定的⼯作。

持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

隔离性: 数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可以防⽌多个事务并发执⾏时由于交叉执⾏⽽导致数据的不⼀致。事务隔离分为不同级别,包括读未提交(Readuncommitted)、读提交(read committed)、可重复读(repeatable read)和串⾏化
(Serializable)。

2.2 Spring 中设置事务隔离级别

Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置.

2.2.1 MySQL 事务隔离级别有 4 种

  1. READ UNCOMMITTED:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,⽽未提交的数据可能会发⽣回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读。
  2. READ COMMITTED:读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执⾏中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读。
  3. REPEATABLE READ:可重复读,是 MySQL 的默认事务隔离级别,它能确保同⼀事务多次查询的结果⼀致。但也会有新的问题,⽐如此级别的事务正在执⾏时,另⼀个事务成功的插⼊了某条数据,但因为它每次查询的结果都是⼀样的,所以会导致查询不到这条数据,⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因)。明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去,这就叫幻读(Phantom Read)。
  4. SERIALIZABLE:序列化,事务最⾼隔离级别,它会强制事务排序,使之不会发⽣冲突,从⽽解决了脏读、不可重复读和幻读问题,但因为执⾏效率低,所以真正使⽤的场景并不多

脏读:⼀个事务读取到了另⼀个事务修改的数据之后,后⼀个事务⼜进⾏了回滚操作,从⽽导致第⼀个事务读取的数据是错的。
不可重复读:⼀个事务两次查询得到的结果不同,因为在两次查询中间,有另⼀个事务把数据修改了。
幻读:⼀个事务两次查询中得到的结果集不同,因为在两次查询中另⼀个事务有新增了⼀部分数据。

2.2.2 Spring 事务隔离级别有 5 种

  1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
  2. Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
  3. Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
  4. Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
  5. Isolation.SERIALIZABLE:串⾏化,可以解决所有并发问题,但性能太低。从上述介绍可以看出,相⽐于 MySQL 的事务隔离级别,Spring 的事务隔离级别只是多了⼀个Isolation.DEFAULT(以数据库的全局事务隔离级别为主)

注意事项:
1. 当 Spring 中设置了事务隔离级别和连接的数据库 (MySQL) 事务隔离级别发生冲突时,那么以 Spring 的为准.
2. Spring 中的事务隔离级别机制的实现是依靠连接数据库支持事务隔离级别为基础.

3. Spring 事务传播机制

事务传播机制: Spring 事务传播机制定义了多个包含了事务的⽅法,相互调⽤时,事务是如何在这些⽅法间进⾏传递的。

事务隔离级别是保证多个并发事务执⾏的可控性的(稳定性的),⽽事务传播机制是保证⼀个事务在多个调⽤⽅法间的可控性的(稳定性的)。

事务隔离级别: 解决的是多个事务同时调用数据库的问题(并发事务)

事务传播机制解决的是⼀个事务在多个节点(⽅法)中传递的问题

3.1 事务传播机制有哪些?

Spring 事务的传播机制包含以下 7 种 :

1. Propagation.REQUIRED: 默认的事务传播级别, 它表示如果当前存在事务, 则加入该事务; 如果当前没有事务, 则创建一个新的事务.

2. Propagation.SUPPORTS: 如果当前存在事务, 则加入该事务; 如果当前没有事务, 则以非事务的方式继续运行.

3. Propagation.MANDATORY: 如果当前存在事务, 则加入该事务; 如果当前没有事务, 则抛出异常.

4. Propagation.REQUIRES_NEW: 表示创建一个新的事务, 如果当前存在事务, 则把当前事务挂起.

5. Propagation.NOT_SUPPORTED: 以非事务方式运行, 如果当前存在事务, 则把当前事务挂起.

6. Propagation.NEVER: 以非事务方式运行, 如果当前存在事务, 则抛出异常.

7. Propagation.NESTED: 如果当前存在事务, 则创建一个事务作为当前事务的嵌套事务来运行; 如果不存在事务, 则创建一个新的事务.

以上 7 种传播⾏为,可以根据是否⽀持当前事务分为以下 3 类:

以情侣关系为例来理解以上分类:

3.2 Spring 事务传播机制使⽤和各种场景演示

3.2.1 ⽀持当前事务(REQUIRED)

先开启事务先成功插⼊⼀条⽤户数据,然后再执⾏⽇志报错,⽽在⽇志报错是发⽣了异常,观察 propagation = Propagation.REQUIRED 的执⾏结果

@Transactional(propagation = Propagation.REQUIRED)@RequestMapping("/add4")public int add4(UserInfo userInfo) {if (userInfo == null ||!StringUtils.hasLength(userInfo.getUsername()) ||!StringUtils.hasLength(userInfo.getPassword())) {return 0;}int userResult = userService.add(userInfo);System.out.println("添加用户:" + userResult);LogInfo logInfo = new LogInfo();logInfo.setName("添加用户");logInfo.setDesc("添加用户结果:" + userResult);int logResult = logService.add(logInfo);return userResult;}

结果:整体都回滚了

3.2.2 不⽀持当前事务(REQUIRES_NEW)

UserController 类中的代码不变,将添加⽤户和添加⽇志的⽅法修改为 REQUIRES_NEW 不⽀持当前事务,重新创建事务

结果:userinfo不受影响,loginfo插入失败

3.2.3 不支持当前事务 (NOT_SUPPORTED)


结果:都添加成功

3.2.4 嵌套事务 ( NESTED)

方法调用流程:
controller/add -> 用户添加方法 -> 日志添加方法

**结果: **
当日志添加方法出现异常之后,嵌套事务的执行结果是:

  1. 用户添加不受影响,添加用户成功
  2. 日志添加失败,因为发生异常回滚了事务

3.2.5 加入事务 (REQUIRED)

方法流程:
controller/add -> 用户添加方法 -> 日志添加方法

结果:
当日志添加方法出现异常之后,加入事务的执行结果是:

  1. 用户添加成功的数据也回滚了
  2. 日志添加数据也回滚了

Spring 事务和事务传播机制相关推荐

  1. Spring事务传播机制大白话(使用springboot,注解演示)

    1. 我对传播机制的理解 为什么需要传播机制? 因为事务之间可能存在相互调用,例如service业务层的方法存在相互调用,如果相互调用的方法都开启了事务(对应到springboot就是在方法上要添加@ ...

  2. java spring 事务传播_spring事务传播机制实例讲解

    天温习spring的事务处理机制,总结如下 对于SQL事务的概念以及ACID性质,可以参见我的另一篇博文 http://kingj.iteye.com/admin/blogs/1675011 spri ...

  3. Spring 事务传播机制 实例讲解

    事务传播机制 对于SQL事务的概念以及ACID性质,可以参见我的另一篇博文 http://kingj.iteye.com/admin/blogs/1675011 spring的管理的事务可以分为如下2 ...

  4. java事务传播机制事例_Spring事务传播机制

    addBook()代码 Java代码  收藏代码 public void addBook() throws Exception{ this.jdbcTemplate.execute(ADD_BOOK) ...

  5. 第09篇:Spring声明式事务的实现方式

    作者: 西魏陶渊明 博客: https://blog.springlearn.cn/ 西魏陶渊明 本篇文章是对Mybatis知识点的一个扩展,主要一起来研究下Spring是如何来管理事务的.顺便再多聊 ...

  6. Spring事务及事务不生效的原因

    目录 注解 `@Transactional` 的属性参数 `Spring` 中事务的传播机制 `Spring` 中事务的隔离级别 常用数据库的隔离级别 `readOnly` 事务的读写性 事务的只读性 ...

  7. spring中的事务到底是什么

    spring事务 事务在代码里或者数据库中都可以配置. 其含义理解为 一系列的数据操作,要么全部执行完成.要么都不执行.归纳为 1.原子性:事务是一个原子操作,由一系列动作组成.事务的原子性确保动作要 ...

  8. spring上下文是什么意思_Java程序员只会CRUD连Spring事务传播机制都不懂?

    AQS到底有什么用?难道就真的只是为了面试吗? 当然不是说AQS没用,如果你不是做基础架构或者中间件开发,你很难感受到AQS的威力.当然,学习很多时候,需要的是正向反馈,学了太多造火箭的东西,面试完就 ...

  9. 原创 | CRUD更要知道的Spring事务传播机制

    来自:肥朝 AQS到底有什么用?难道就真的只是为了面试吗? 当然不是说AQS没用,如果你不是做基础架构或者中间件开发,你很难感受到AQS的威力.当然,学习很多时候,需要的是正向反馈,学了太多造火箭的东 ...

  10. Spring事务传播机制和隔离级别

    Spring有5种隔离级别,7种传播行为.这是面试常问的内容,也是代码中经常碰到的知识点.这些知识枯燥而且乏味,其中有些非常的绕.如果栽在这上面,就实在是太可惜了. @Transactional(is ...

最新文章

  1. CentOS7 network
  2. torch模拟sigmoid
  3. Spark学习之路 (十五)SparkCore的源码解读(一)启动脚本
  4. c语言第一章考试题及答案,C语言考试题库及答案整理版.doc
  5. Python import导入模块与函数方法 Python语言基础【1】
  6. UEFI 引导与 BIOS 引导
  7. php 怎么复制一个文件,php如何复制文件夹?
  8. linux 内核移植(七)――rest_init函数分析
  9. lisp 吴永进_AutoCAD 完全应用指南
  10. adobe premiere 不支持的视频驱动程序
  11. Unity 如何获取安卓设备的SN号
  12. java快捷键和快捷指令(基于scode)
  13. Android 日历表事件表操作
  14. 凌晨起来肝的一篇 Java 学习路线,保证学弟学妹们大三大四的时候顺利找到实习 Offer
  15. 写给冬天里开放的那些花儿
  16. IKBC W200 键盘 win 键失效
  17. 这不是结束,而是另一次重逢的开始
  18. 西安交大计算机考研软件工程编程题库(二十四)
  19. 操作系统笔记(二):进程和线程
  20. 基于stm32的温湿度检测案例(一)

热门文章

  1. 【Spark】Spark的机器学习算法库——Spark MLilb
  2. SECS/GEM 基本概念介绍
  3. 小爱同学控制ESP8266点灯
  4. jdk10和jdk8共存和快速切换
  5. win10易升_win10性能模式是什么?怎么开启?
  6. azw3转换为pdf_PDF怎么转换为PPT?PDF秒转PPT秘技!
  7. 对于文件编码格式的浅显理解
  8. php 批量打印word pdf,Office批量打印精灵教程--Word、PDF、Excel、PPT批量打印
  9. magisk卸载内置软件_安卓刷XP框架 手机通用通用(Magisk+Riru+EdXposed)
  10. java 当天日期 dateutil_Java时间日期DateUtil