Spring事务和事务传播机制

📅 发布时间:2026/8/9 15:46:18
Spring事务和事务传播机制 (一).回顾事务之前在MySQL阶段就已经介绍过事务的概念了。事务是一组操作的集合是一个不可分割的操作。事务会把所有的操作作为一个整体一起向数据库提交或者撤销操作请求所以这组操作要么同时成功要么同时失败。具体的可以看MySQL专栏关于事务的介绍。(二).Spring中事务的实现1.数据准备这里就拿“用户注册”为示例当用户注册时在日志表中插入一条操作记录(1).SQL脚本-- 创建数据库 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;(2).Controller层代码package org.com.spring_trans.spring_trans_blog.controller; import lombok.extern.slf4j.Slf4j; import org.com.spring_trans.spring_trans_blog.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; Slf4j RestController RequestMapping(/user1) public class UserController1 { Autowired private UserService userService; RequestMapping(/registry) public String registry (String userName,String password){ Integer result userService.registryUser(userName, password); log.info(注册成功); return 注册成功; } }(3).Service层代码package org.com.spring_trans.spring_trans_blog.service; import lombok.extern.slf4j.Slf4j; import org.com.spring_trans.spring_trans_blog.mapper.UserInfoMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; Service Slf4j public class UserService { Autowired private UserInfoMapper userInfoMapper; public Integer registryUser(String userName,String password){ //插入用户信息 return userInfoMapper.insert(userName,password); } }package org.com.spring_trans.spring_trans_blog.service; import lombok.extern.slf4j.Slf4j; import org.com.spring_trans.spring_trans_blog.mapper.LogInfoMapper; import org.com.spring_trans.spring_trans_blog.mapper.UserInfoMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; Service Slf4j public class LogService { Autowired private LogInfoMapper logInfoMapper; public Integer insertLog(String userName,String op){ //记录用户操作 return logInfoMapper.insert(userName,op); } }(3).Mapper层代码package org.com.spring_trans.spring_trans_blog.mapper; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; Mapper public interface UserInfoMapper { Insert(insert into user_info (user_name,password) values (#{userName},#{password})) Integer insert(String userName,String password); }package org.com.spring_trans.spring_trans_blog.mapper; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; Mapper public interface LogInfoMapper { Insert(insert into user_info (user_name,op) values (#{userName},#{op})) Integer insert(String userName,String op); }(4).Model层代码package org.com.spring_trans.spring_trans_blog.model; import lombok.Data; import java.util.Date; Data public class UserInfo { private Integer id; private String userName; private String password; private Date createTime; private Date updateTime; }package org.com.spring_trans.spring_trans_blog.model; import lombok.Data; import java.util.Date; Data public class LogInfo { private Integer id; private String userName; private String op; private Date createTime; private Date updateTime; }2.事务的分类Spring中的事务操作有两种第一种是“编程式事务”需要手动写代码操作事务第二种是“声明式事务”利用注解自动开启和提交事务。(1).编程式事务主要有三个步骤①.开启事务 ②.提交事务 ③.回滚事务具体在代码里呈现当程序运行的时候此时发现是可以成功的同样也可以进行回滚事务可以发现虽然返回的是“注册成功”但是在数据库中并没有记录(2).声明式事务声明式事务使用的是Transactional注解我们只需要在要添加事务的方法上加上Transactional注解就可以实现了不需要手动开启事务和提交事务进入方法时会自动开启事务方法执行完毕后会自动提交事务如果中途发生了没有处理的异常会自动回滚事务可以看到程序正常运行现在我手动添加了一个异常但是并没有进行处理当我再进行测试可以看到后端报错了并且数据库中也没有对应的记录当我对异常进行了捕获此时再进行测试可以看到当我对异常进行了捕获之后事务也是可以正常提交的(三)..Transactional介绍上图是Transactional注解中的属性。这里只介绍里面比较重要的属性1.rollbackForrollbackFor指的是异常回滚属性用于指定能够触发事务回滚的异常类型可以指定多个异常类型Transactional默认旨在遇到运行时异常和Error时才进行回滚非运行时异常不回滚。当我抛出一个IO 输入输出受检异常的时候再进行测试虽然报错了但是事务没有进行回滚数据也插入到了数据库中。如果想要进行设置那么就需要在Transactional中添加相应的属性通过rollbackFor属性来执行出现IOException时事务进行回滚可以发现这次事务就进行了回滚2.isolationisolation指的是事务的隔离级别是为了解决多个事务同时调用一个数据库的问题Spring事务的隔离级别和MySQL事务的隔离级别类似(1).MySQL事务隔离级别MySQL的隔离级别分为四类读未提交读已提交可重复读串行化Ⅰ.读未提交读未提交指的是事务2可以读取到事务1中未提交的数据Ⅱ.读已提交读已提交指的是事务2可以读取到事务1已经提交的数据Ⅲ.可重复读可重复读指的是事务2不会读到事务1对已有数据的修改即使事务1已经提交了也是读不到的。这样也就可以确保同一事务多次查询的结果一致。(快照读)但是也会产生“幻读”(当前读)问题Ⅳ.串行化串行化也称为“序列化”事务的最高隔离级别。它会强制事务排序使其不发生冲突从而解决脏读不可重复读幻读问题。但是会导致执行效率低(2).Spring事务隔离级别Spring事务的隔离级别有5种和MySQL的隔离级别一样在Transactional注解中可以进行设置3.propagationpropagation为“事务传播机制”下面开始详细介绍(1).什么是事务传播机制事务传播机制就是多个方法存在调用关系时事务是如何在这些方法间进行传播的例如现在controller层方法A调用Service层的B方法A方法运行时会开启一个事务B方法本身也有事务此时B方法运行时到底是加入A的事务还是B创建一个新的事务这就涉及到了“事务的传播机制”了“事务的传播机制”是为了解决一个事务在多个节点或者方法中传递的问题(2).事务的传播机制有哪些Spring事务的传播机制有七种(3).传播机制的使用场景Ⅰ.REQUIRED现在三个方法上的事务传播机制我都设置成了REQUIRED当程序运行起来的时候通过日志可以发现自始至终使用的都是一个事务当我在Controller层的方法里面手动加了一个异常后再运行可以看到当发生异常之后并没有进行提交事务而是进行了回滚并释放和关闭了会话因为在Controller层发生了异常Service层的两个方法和Controller层使用的同一个事务所以Service层的两个方法也回滚了Ⅱ.REQUIRES_NEW现在Contoller层的事务传播机制依旧为REQUIRED然后Service层的两个方法的事务传播机制设置为了REQUIRES_NEW当程序运行起来的时候可以看到UserService的事务在增加完数据之后就提交了同样LogSerivce也是可能有人会问Controller层不是还有事务的吗这是因为Controller层的事务并没有执行数据库操作不会生曾SqlSession所以控制台看不到它现在我在LogService层的方法中创建了一个异常现在再进行测试可以看到虽然报错了但是user_info表中是可以插入数据的log_info 表中数据是没有插入成功的。通过日志可以发现在logService抛异常之前UserService中的那个事务已经提交了并没有涉及到LogService这也证实了REQUIRES_NEW级别的传播机制事务和事务之间是互不影响的当我在controller层创建了一个异常可以看到虽然抛出了异常但是抛出异常之前UserService和LogService对应的事务都已经提交了并没有产生影响Ⅲ.NEVER此时我将UserService中的方法的事务传播机制改成了NEVER发现直接报错了因为在controller层是有一个事务的当执行到UserService的时候发现当前存在事务此时就直接报错了。Ⅳ.NESTED此时我将UserService 和 LogService 的事务传播机制都修改成了 NESTED然后开始测试可以发现只有方法执行完成之后最后提交的事务并且是一个事务。当我在LogService中添加一个异常之后再进行测试可以发现事务并没有进行提交而是进行了回滚操作那么测试到这可以发现这个NESTED 看起来和 REQUIRED 没什么区别实则不然下面继续修改代码现在我修改我的代码当产生异常的时候进行捕获然后回滚事务。此时再进行测试可以发现user_info 表数据添加成功log_info表添加失败这就看出NESTED 和 REQUIRED 的区别来了LogService中的事务发生了回滚但是嵌套事务不会回滚到嵌套之前的事务也就是说嵌套事务可以实现部分事务回滚。Ⅴ.NESTED 和 REQUIRED的区别①.如果真个事务全部执行成功则二者的结果是一样的②.如果事务一部分执行成功一部分发生回滚那么REQUIRED加入事务会导致整个事务全部回滚NESTED嵌套事务可以实现局部回滚不会影响上一个方法中执行的结果③.嵌套事务之所以能够实现事物的部分回滚是因为事务中有一个保存点(savepoint)的概念嵌套事务进入之后相当于新建了一个保存点所以回滚时之回滚到当前保存点。④.REQUIRED是加入当前事务中并没有创建事务的保存点因此出现了回滚就是回滚整个事务。Ⅵ.NESTED 和 REQUIRES_NEW的区别①.NESTED是同一个事务依靠保存点分段REQUIRES_NEW是两个独立的事务②.NETED是如果当前存在事务则创建一个事务作为当前事务的嵌套事务依托外层主事务的保存点实现③.REQUIRES_NEW是创建一个新的事务如果当前存在事务则将当前事务挂起无论外部方法是否开启事务REQUIRES_NEW修饰的内部方法都会开启自己的事务且开启的事务相互独立互不影响。④.对于NESTED来说如果内部发生异常并且进行了捕获此时只会进行局部回滚回滚到嵌套的保存点外层捕获异常可以继续往下执行如果外部发生异常则直接全部回滚所有嵌套的子事务强制一起回滚⑤.REQUIRES_NEW来说外部事务回滚不影响内层事务如果内部报错回滚那么外部捕获异常之后依然可以正常提交