一、事务
1.1、什么是事务
事务是一组操作的集合, 是一个不可分割的操作.
事务会把所有的操作作为一个整体, 一起向数据库提交或者是撤销操作请求. 所以这组操作要么同时成功,要么同时失败。
1.2、为什么需要事务
我们在进行程序开发时, 也会有事务的需求.
比如转账操作: 第一步:a 账户 -100 元. 第二步:b 账户 +100 元. 如果没有事务,第⼀步执行成功了, 第⼆步执行失败了, 那么a 账户的100 元就平白无故消失了. 如果使 用事务就可以解决这个问题, 让这⼀组操作要么⼀起成功, 要么一起失败.
比如秒杀系统,
第一步: 下单成功
第二步: 扣减库存
下单成功后, 库存也需要同步减少. 如果下单成功, 库存扣减失败, 那么就会造成下单超出的情况. 所以就需要把这两步操作放在同一个事务中. 要么一起成功, 要么一起失败。
1.3、事务的操作
事务的操作主要有三步:
1. 开启事start transaction/ begin (⼀组操作前开启事务)
2. 提交事务: commit (这组操作全部成功, 提交事务)
3. 回滚事务: rollback (这组操作中间任何⼀个操作出现异常, 回滚事务)
-- 开启事务 start transaction; -- 提交事务 commit; -- 回滚事务 rollback;
二、spring中事务的实现
spring 中的事务操作分为两类:
- 编程式事务(手动写代码操作事务).
- 声明式事务(利用注解自动开启和提交事务).
在了解事务之前,我们先准备数据和数据的访问代码:
需求: 用户注册, 注册时在日志表中插入⼀条操作记录:
数据准备:
-- 创建数据库 drop database if exists trans_test; create database trans_test default character set utf8mb4; -- ⽤⼾表 drop table if exists user_info; create table user_info ( `id` int not null auto_increment, `user_name` varchar (128) not null, `password` varchar (128) not null, `create_time` datetime default now(), `update_time` datetime default now() on update now(), primary key (`id`) ) engine = innodb default character set = utf8mb4 comment = '用户表'; -- 操作⽇志表 drop table if exists log_info; create table log_info ( `id` int primary key auto_increment, `user_name` varchar ( 128 ) not null, `op` varchar ( 256 ) not null, `create_time` datetime default now(), `update_time` datetime default now() on update now() ) default charset 'utf8mb4';
代码准备:
1.创建项目 spring-trans, 引⼊spring web, mybatis, mysql等依赖
2 . 配置文件:
spring: datasource: url: jdbc:mysql://127.0.0.1:3306/trans_testcharacterencoding=utf8&usessl=false username: root password: root driver-class-name: com.mysql.cj.jdbc.driver mybatis: configuration: # 配置打印 mybatis⽇志 log-impl: org.apache.ibatis.logging.stdout.stdoutimpl map-underscore-to-camel-case: true #配置驼峰⾃动转换
3.实体类
@data
public class userinfo {
private integer id;
private string username;
private string password;
private date createtime;
private date updatetime;
}
@data
public class loginfo {
private integer id;
private string username;
private string op;
private date createtime;
private date updatetime;
}
4.mapper
@mapper
public interface userinfomapper {
@insert("insert into user_info(`user_name`,`password`)values(#{name},#
{password})")
integer insert(string name, string password);
}
@mapper
public interface loginfomapper {
@insert("insert into log_info(`user_name`,`op`)values(#{name},#{op})")
integer insertlog(string name, string op);
}5.service
@slf4j
@service
public class userservice {
@autowired
private userinfomapper userinfomapper;
public void registryuser(string name, string password) {
//插⼊⽤⼾信息
userinfomapper.insert(name, password);
}
}
@slf4j
@service
public class logservice {
@autowired
private loginfomapper loginfomapper;
public void insertlog(string name, string op) {
//记录⽤⼾操作
loginfomapper.insertlog(name, "⽤户注册");
}
}6.controller
@requestmapping("/user")
@restcontroller
public class usercontroller {
@autowired
private userservice userservice;
@requestmapping("/registry")
public string registry(string name, string password) {
//⽤⼾注册
userservice.registryuser(name, password);
return "注册成功";
}
}2.1、spring编程式事务
spring 手动操作事务和上面 mysql 操作事务类似, 有 3 个重要操作步骤:
• 开启事务(获取事务)
• 提交事务
• 回滚事务
springboot 内置了两个对象:
- datasourcetransactionmanager 事务管理器. 用来获取事务(开启事务), 提交或回滚事务的
- transactiondefinition 是事务的属性, 在获取事务的时候需要将transactiondefinition 传递进去从⽽获得⼀个事务 transactionstatus。
我们还是根据代码的实现来学习:
@requestmapping("/user")
@restcontroller
public class usercontroller {
// jdbc 事务管理器
@autowired
private datasourcetransactionmanager datasourcetransactionmanager;
// 定义事务属性
@autowired
private transactiondefinition transactiondefinition;
@autowired
private userservice userservice;
@requestmapping("/registry")
public string registry(string name, string password) {
// 开启事务
transactionstatus transactionstatus = datasourcetransactionmanager
.gettransaction(transactiondefinition);
//⽤⼾注册
userservice.registryuser(name, password);
//提交事务
datasourcetransactionmanager.commit(transactionstatus);
//回滚事务
//datasourcetransactionmanager.rollback(transactionstatus);
return "注册成功";
}
}运⾏程序之后,用postman测试接口,观看数据库结果数据插入成功。

观察事务回滚:
//回滚事务 datasourcetransactionmanager.rollback(transactionstatus);
观察数据库, 虽然程序返回"注册成功", 但数据库并没有新增数据.
以上代码虽然可以实现事务, 但操作也很繁琐, 有没有更简单的实现⽅法呢? 接下来我们学习声明式事务。
2.2、spring 声明式事务 @transactional
声明式事务的实现很简单, 只需要在需要事务的放法上添加 @transactional 注解就可以实现了. 无需手动开启事务和提交事务, 进入方法时自动开启事务, 方法执行完会自动提交事务, 如果中途发生了 没有处理的异常会自动回滚事务.
我们来看代码实现:
@requestmapping("/trans")
@restcontroller
public class transactionalcontroller {
@autowired
private userservice userservice;
@transactional
@requestmapping("/registry")
public string registry(string name, string password) {
//⽤⼾注册
userservice.registryuser(name, password);
return "注册成功";
}
}运⾏程序, 发现数据插⼊成功.
修改程序, 使之出现异常
@slf4j
@requestmapping("/trans")
@restcontroller
public class transactionalcontroller {
@autowired
private userservice userservice;
@transactional
@requestmapping("/registry")
public string registry(string name, string password) {
//⽤⼾注册
userservice.registryuser(name, password);
log.info("⽤⼾数据插⼊成功");
//强制程序抛出异常
int a = 10 / 0;
return "注册成功";
}
}运行程序: 发现虽然日志显示数据插⼊成功, 但数据库却没有新增数据, 事务进行了回滚.

@transactional 作⽤
@transactional 可以用来修饰方法或类:
• 修饰方法时: 只有修饰public方法时才生效(修饰其他方法时不会报错, 也不生效)
• 修饰类时: 对 @transactional 修饰的类中所有的 public 方法都生效
方法/类被 @transactional 注解修饰时, 在目标方法执行开始之前, 会⾃动开启事务, ⽅法执行结束 之后, 自动提交事务.
如果在方法执行过程中, 出现异常, 且异常未被捕获, 就进行事务回滚操作.
如果异常被程序捕获, 方法就被认为是成功执行, 依然会提交事务.
修改上述代码, 对异常进⾏捕获:
@transactional
@requestmapping("/registry")
public string registry(string name, string password) {
//⽤⼾注册
userservice.registryuser(name, password);
log.info("⽤⼾数据插⼊成功");
//对异常进⾏捕获
try {
//强制程序抛出异常
int a = 10 / 0;
} catch (exception e) {
e.printstacktrace();
}
return "注册成功";
}运⾏程序, 发现虽然程序出错了, 但是由于异常被捕获了, 所以事务依然得到了提交. 如果需要事务进⾏回滚, 有以下两种方式:
- 重新抛出异常
@transactional
@requestmapping("/registry")
public string registry(string name, string password) {
//⽤⼾注册
userservice.registryuser(name, password);
log.info("⽤⼾数据插⼊成功");
//对异常进⾏捕获
try {
//强制程序抛出异常
int a = 10 / 0;
} catch (exception e) {
//将异常重新抛出去
throw e;
}
return "注册成功";
}- 手动回滚事务
使用 transactionaspectsupport.currenttransactionstatus() 得到当前的事务, 并 使⽤ setrollbackonly 设置 setrollbackonly。
@transactional
@requestmapping("/registry")
public string registry(string name, string password) {
//⽤⼾注册
userservice.registryuser(name, password);
log.info("⽤⼾数据插⼊成功");
//对异常进⾏捕获
try {
//强制程序抛出异常
int a = 10 / 0;
} catch (exception e) {
// ⼿动回滚事务
transactionaspectsupport.currenttransactionstatus().setrollbackonly();
}
return "注册成功";
}三、@transactional 详解
通过上面的代码, 我们学习了 @transactional 的基本使用. 接下来我们学习 @transactional
注解的使用细节. 我们主要学习 @transactional 注解当中的三个常见属性:
- rollbackfor: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型
- isolation: 事务的隔离级别. 默认值为 isolation.default
- propagation: 事务的传播机制. 默认值为 propagation.required
3.1、rollbackfor
@transactional 默认只在遇到运行时异常和error时才会回滚, 非运行时异常不回滚. 即exception的子类中, 除了runtimeexception及其子类。
如果我们需要所有异常都回滚, 需要来配置 @transactional 注解当中的 rollbackfor 属性, 通过 rollbackfor 这个属性指定出现何种异常类型时事务进行回滚。
@transactional(rollbackfor = exception.class)
@requestmapping("/r2")
public string r2(string name, string password) throws ioexception {
//⽤⼾注册
userservice.registryuser(name, password);
log.info("⽤⼾数据插⼊成功");
if (true) {
throw new ioexception();
}
return "r2";
}
运行程序后,发现虽然程序抛出了异常, 但是事务依然进⾏了提交.
结论:
• 在spring的事务管理中,默认只在遇到运行时异常runtimeexception和error时才会回滚.
• 如果需要回滚指定类型的异常, 可以通过rollbackfor属性来指定.
3.2事务隔离级别
3.2.1、mysql 事务隔离级别
sql 标准定义了四种隔离级别, mysql 全都⽀持. 这四种隔离级别分别是:
- 读未提交(read uncommitted): 读未提交, 也叫未提交读. 该隔离级别的事务可以看到其他事务中 未提交的数据. 因为其他事务未提交的数据可能会发生回滚, 但是该隔离级别却可以读到, 我们把该级别读到的数 据称之为脏数据, 这个问题称之为脏读.
2 . 读提交(read committed): 读已提交, 也叫提交读. 该隔离级别的事务能读取到已经提交事务的数 据,该隔离级别不会有脏读的问题.但由于在事务的执⾏中可以读取到其他事务提交的结果, 所以在不 同时间的相同 sql 查询可能会得到不同的结果, 这种现象叫做不可重复读
3 . 可重复读(repeatable read): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也 就可以确保同⼀事务多次查询的结果⼀致, 但是其他事务新插⼊的数据, 是可以感知到的. 这也就引 发了幻读问题. 可重复读, 是 mysql 的默认事务隔离级别. 比如此级别的事务正在执行时, 另⼀个事务成功的插⼊了某条数据, 但因为它每次查询的结果都是⼀样的, 所以会导致查询不到这条数据, 自己重复插入时又失败(因为唯⼀约束的原因). 明明在事务中查询不到这条信息,但自己就是插入不进去, 这个现象叫幻读.
4 . 串行化(serializable): 序列化, 事务最高隔离级别. 它会强制事务排序, 使之不会发生冲突, 从而解决了脏读, 不可重复读和幻读问题, 但因为执行效率低, 所以真正使用的场景并不多.
3.2.2、spring 事务隔离级别
spring 中事务隔离级别有5 种:
- isolation.default : 以连接的数据库的事务隔离级别为主.
2 . isolation.read_uncommitted : 读未提交, 对应sql标准中 read uncommitted
3 . isolation.read_committed : 读已提交,对应sql标准中 read committed
4 . isolation.repeatable_read : 可重复读, 对应sql标准中 repeatable read
5 . isolation.serializable : 串⾏化, 对应sql标准中 serializable
public enum isolation {
default(-1),
read_uncommitted(1),
read_committed(2),
repeatable_read(4),
serializable(8);
private final int value;
private isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
spring 中事务隔离级别可以通过 @transactional 中的 isolation 属性进行设置。
@transactional(isolation = isolation.read_committed)
@requestmapping("/r3")
public string r3(string name,string password) throws ioexception {
//... 代码省略
return "r3";
}
3.3、spring 事务传播机制
3.3.1、什么是事务传播机制?
事务传播机制就是: 多个事务方法存在调用关系时, 事务是如何在这些方法间进行传播的?
比如有两个方法a, b都被 @transactional 修饰, a方法调用b方法 a方法运行时, 会开启⼀个事务. 当a调用b时, b方法本身也有事务, 此时b方法运行时, 是加入a的事务, 还 是创建一个新的事务呢? 这个就涉及到了事务的传播机制。
比如公司流程管理
执行任务之前, 需要先写执行⽂档, 任务执行结束, 再写总结汇报

此时a部门有⼀项工作, 需要b部门的支援, 此时b部门是直接使用a部门的⽂档, 还是新建一个文档呢?
事务隔离级别解决的是多个事务同时调用一个数据库的问题:

而事务传播机制解决的是⼀个事务在多个节点(⽅法)中传递的问题:
3.3.2、事务的传播机制有哪些
@transactional 注解支持事务传播机制的设置, 通过 propagation 属性来指定传播⾏为.spring 事务传播机制有以下 7 种:
- propagation.required : 默认的事务传播级别. 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则创建一个新的事务.
2 . propagation.supports : 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则以非事务的方式继续运行.
3 . propagation.mandatory :强制性. 如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则 抛出异常.
4 . propagation.requires_new : 创建⼀个新的事务. 如果当前存在事务, 则把当前事务挂起. 也 就是说不管外部⽅法是否开启事务, propagation.requires_new 修饰的内部方法都会新开 启⾃⼰的事务, 且开启的事务相互独立, 互不干扰.
5 . propagation.not_supported : 以非事务方式运行, 如果当前存在事务, 则把当前事务挂起(不用).
6 . propagation.never : 以非事务方式运行, 如果当前存在事务, 则抛出异常.
7 . propagation.nested : 如果当前存在事务, 则创建⼀个事务作为当前事务的嵌套事务来运行. 如果当前没有事务, 则该取值等价于 propagation_required .
public enum propagation {
required(0),
supports(1),
mandatory(2),
requires_new(3),
not_supported(4),
never(5),
nested(6);
private final int value;
private propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}3.3.3、spring 事务传播机制使用和各种场景演示
对于以上事务传播机制,我们重点关注以下两个就可以了:
- required(默认值)
- requires_new
3.3.3.1 required(加入事务)
看下⾯代码实现:
- 用户注册, 插入一条数据
- 记录操作日志, 插入一条数据(出现异常)
观察 propagation = propagation.required 的执行结果:
@requestmapping("/propaga")
@restcontroller
public class propagationcontroller {
@autowired
private userservice userservice;
@autowired
private logservice logservice;
@transactional(propagation = propagation.required)
@requestmapping("/p1")
public string r3(string name, string password) {
//⽤⼾注册
userservice.registryuser(name, password);
//记录操作⽇志
logservice.insertlog(name, "⽤⼾注册");
return "r3";
}
}对应的userservice和logservice都添加上 @transactional(propagation =
propagation.required)。
@slf4j
@service
public class userservice {
@autowired
private userinfomapper userinfomapper;
@transactional(propagation = propagation.required)
public void registryuser(string name, string password) {
//插⼊⽤⼾信息
userinfomapper.insert(name, password);
}
}
@slf4j
@service
public class logservice {
@autowired
private loginfomapper loginfomapper;
@transactional(propagation = propagation.required)
public void insertlog(string name, string op) {
int a = 10 / 0;
//记录⽤⼾操作
loginfomapper.insertlog(name, "⽤⼾注册");
}
}运行程序, 发现数据库没有插入任何数据. 流程描述:
- p1 方法开始事务
- 用户注册, 插入一条数据 (执行成功) (和p1 使用同⼀个事务)
- 记录操作日志, 插入一条数据(出现异常, 执行失败) (和p1 使用同⼀个事务)
- 因为步骤3出现异常, 事务回滚. 步骤2和3使用同⼀个事务, 所以步骤2的数据也回滚了.
3.3.3.2、requires_new(新建事务)
将上述userservice 和logservice 中相关⽅法事务传播机制改为propagation.requires_new。
@service
public class userservice {
@autowired
private userinfomapper userinfomapper;
@transactional(propagation = propagation.requires_new)
public void registryuser(string name, string password) {
//插⼊⽤⼾信息
userinfomapper.insert(name, password);
}
}
@service
public class logservice {
@autowired
private loginfomapper loginfomapper;
@transactional(propagation = propagation.requires_new)
public void insertlog(string name, string op) {
int a = 10 / 0;
//记录⽤⼾操作
loginfomapper.insertlog(name, "⽤⼾注册");
}
}运行程序, 发现用户数据插入成功了, 日志表数据插入失败.
logservice 方法中的事务不影响 userservice 中的事务.
当我们不希望事务之间相互影响时, 可以使用该传播行为.
3.3.3、never (不支持当前事务, 抛异常)
修改userservice 中对应⽅法的事务传播机制为 propagation.never
@slf4j
@service
public class userservice {
@autowired
private userinfomapper userinfomapper;
@transactional(propagation = propagation.never)
public void registryuser(string name, string password) {
//插⼊⽤⼾信息
userinfomapper.insert(name, password);
}
}程序执行报错, 没有数据插入.
3.3.3.4、nested(嵌套事务)
将上述userservice 和logservice 中相关方法事务传播机制改为 propagation.nested。
@slf4j
@service
public class userservice {
@autowired
private userinfomapper userinfomapper;
@transactional(propagation = propagation.nested)
public void registryuser(string name, string password) {
//插⼊⽤⼾信息
userinfomapper.insert(name, password);
}
}
@slf4j
@service
public class logservice {
@autowired
private loginfomapper loginfomapper;
@transactional(propagation = propagation.nested)
public void insertlog(string name, string op) {
int a = 10 / 0;
//记录⽤⼾操作
loginfomapper.insertlog(name, "⽤⼾注册");
}
}运⾏程序, 发现没有任何数据插入. 流程描述:
- controller 中p1 方法开始事务
- userservice 用户注册, 插入一条数据 (嵌套p1事务)
- logservice 记录操作日志, 插入一条数据(出现异常, 执行失败) (嵌套p1事务, 回滚当前事务, 数据添加失败)
- 由于是嵌套事务, logservice 出现异常之后, 往上找调用它的方法和事务, 所以用户注册也失败了.
- 最终结果是两个数据都没有添加
p1事务可以认为是父事务, 嵌套事务是子事务. 父事务出现异常, 子事务也会回滚, 子事务出现异常, 如果不进行处理, 也会导致父事务回滚.
3.3.3.5 nested和required 有什么区别?
我们在 logservice 进⾏当前事务回滚, 修改 logservice 代码如下:
@service
public class logservice {
@autowired
private loginfomapper loginfomapper;
@transactional(propagation = propagation.nested)
public void insertlog(string name, string op) {
try {
int a = 10 / 0;
} catch (exception e) {
//回滚当前事务
transactionaspectsupport.currenttransactionstatus().setrollbackonly();
}
//记录⽤⼾操作
loginfomapper.insertlog(name, "⽤⼾注册");
}
}重新运行程序, 发现用户表数据添加成功, 日志表添加失败.
logservice 中的事务已经回滚, 但是嵌套事务不会回滚嵌套之前的事务, 也就是说嵌套事务可以实现部分事务回滚。
对⽐required
把 nested 传播机制改为 required, 修改代码如下:
@service
public class userservice {
@autowired
private userinfomapper userinfomapper;
@transactional(propagation = propagation.required)
public void registryuser(string name, string password) {
//插⼊⽤⼾信息
userinfomapper.insert(name, password);
}
}
@service
public class logservice {
@autowired
private loginfomapper loginfomapper;
@transactional(propagation = propagation.required)
public void insertlog(string name, string op) {
try {
int a = 10 / 0;
} catch (exception e) {
//回滚当前事务
transactionaspectsupport.currenttransactionstatus().setrollbackonly();
}
//记录⽤⼾操作
loginfomapper.insertlog(name, "⽤⼾注册");
}
}重新运行程序, 发现用户表和日志表的数据添加都失败了.
required 如果回滚就是回滚所有事务, 不能实现部分事务的回滚. (因为属于同⼀个事务)
nested和required区别:
• 整个事务如果全部执行成功, ⼆者的结果是⼀样的.
• 如果事务⼀部分执行成功, required加入事务会导致整个事务全部回滚. nested嵌套事务可以实现局部回滚, 不会影响上一个方法中执行的结果.
嵌套事务之所以能够实现部分事务的回滚, 是因为事务中有⼀个保存点(savepoint)的概念, 嵌套事务 进入之后相当于新建了⼀个保存点, 而滚回时只回滚到当前保存点。
required 是加入到当前事务中, 并没有创建事务的保存点, 因此出现了回滚就是整个事务回滚, 这就是嵌套事务和加入事务的区别
总结
- spring中使用事务, 有两种方式: 编程式事务(手动操作)和声明式事务. 其中声明式事务使用较多,在方法上添加 @transactional 就可以实现了
- 通过 @transactional(isolation = isolation.serializable) 设置事务的隔离级别. spring 中的事务隔离级别有 5 种
- 通过 @transactional(propagation = propagation.required) 设置事务的传播机制, spring 中的 事务传播级别有 7 种, 重点关注 required (默认值) 和 requires_new
以上就是本文全部内容,感谢各位能够看到最后,如有问题,欢迎各位大佬在评论区指正,希望大家可以有所收获!创作不易,希望大家多多支持!
到此这篇关于spring事务和事务传播机制操作大全的文章就介绍到这了,更多相关spring事务传播机制内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论