目录
🚩事务
🎈为什么需要事务?
🎈事务的操作
🚩Spring 中事务的实现
🎈数据准备
🎈Spring 编程式事务(了解)
🎈Spring 声明式事务 @Transactional
🍭@Transactional 详解
📝rollbackFor
❗结论(异常捕获问题)
📝spring事务隔离级别
📝Spring 事务传播机制
✅Propagation.REQUIRED(事务之间互相影响)
✅Propagation.REQUIRED_NEW(新建事务,互不影响)
✅Propagation.NEVER (不⽀持当前事务, 抛异常)
✅Propagation.NESTED(嵌套事务)
👩🏻💻NESTED和REQUIRED有什么区别?
🚩总结
🚩事务
🎈为什么需要事务?
我们在进行程序的开发的时候,也会有事务的需求。
比如转账的操作:
第⼀步:A 账⼾ -100 元.第⼆步:B 账⼾ +100 元.如果没有事务,第⼀步执⾏成功了, 第⼆步执⾏失败了, 那么A 账⼾的100 元就平⽩⽆故消失了. 如果使⽤事务就可以解决这个问题, 让这⼀组操作要么⼀起成功, 要么⼀起失败.
🎈事务的操作
事务的操作主要有三步:1. 开启事务: start transaction/ begin (⼀组操作前开启事务)2. 提交事务: commit (这组操作全部成功, 提交事务)3. 回滚事务: rollback (这组操作中间任何⼀个操作出现异常, 回滚事务)
-- 开启事务start transaction;-- 提交事务commit ;-- 回滚事务rollback ;
🚩Spring 中事务的实现
Spring 中的 事务操作 分为两类:1. 编程式事务(⼿动写代码操作事务).2. 声明式事务(利⽤注解⾃动开启和提交事务)
🎈数据准备
0>需求: ⽤⼾注册, 注册时在⽇志表中插⼊⼀条操作记录:1>数据准备:-- 数据库 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>代码准备:1. 创建项⽬ spring-trans, 引⼊Spring Web, Mybatis, mysql等依赖2. 配置⽂件spring: datasource: url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=false username: 用户名 password: 密码 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 #日志配置 server: port: 9090
3. 实体类@Data public class LogInfo { private Integer id; private String userName; private String op; private Data createTime; private Data updateTime; }
@Data public class UserInfo { private Integer id; private String userName; private String password; private Data createTime; private Data updateTime; }
4. Mapper@Mapper public interface UserInfoMapper { @Insert("insert into user_info (user_name,password)values (#{userName},#{password})") Integer insert(String userName,String password); }
@Mapper public interface LogInfoMapper { @Insert("insert into log_info (user_name,op)values (#{userName},#{op})") Integer insertLog(String userName,String op); }
5. Service@Service public class LogService { @Autowired private LogInfoMapper logInfoMapper; public Integer insert(String userName, String op) { Integer result= logInfoMapper.insertLog(userName,op); return result; } }
@Service public class UserService { @Autowired private UserInfoMapper userInfoMapper; public Integer insert(String userName, String password) { return userInfoMapper.insert(userName,password); } }
🎈Spring 编程式事务(了解)
Spring ⼿动操作事务和上⾯ MySQL 操作事务类似, 有 3 个重要操作步骤:
- • 开启事务(获取事务)
- • 提交事务
- • 回滚事务
SpringBoot 内置了两个对象:
- 1. DataSourceTransactionManager 事务管理器. ⽤来获取事务(开启事务), 提交或回滚事务的
- 2. TransactionDefinition 是事务的属性, 在获取事务的时候需要将 TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus
@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;//事务管理器
@Autowired
private TransactionDefinition transactionDefinition;//定义事务属性
@Autowired
private UserService userService;
@RequestMapping("/register")
public Boolean register(String userName, String password){
//开启事务
TransactionStatus transaction=dataSourceTransactionManager.getTransaction(transactionDefinition);
//用户注册
Integer result=userService.insert(userName,password);
System.out.println("插入用户表,result: "+result);
//提交事务
dataSourceTransactionManager.commit(transaction);
return true;
}
}
提交事务:
事务回滚:数据库信息未更新
@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;//事务管理器
@Autowired
private TransactionDefinition transactionDefinition;//定义事务属性
@Autowired
private UserService userService;
@RequestMapping("/register")
public Boolean register(String userName, String password){
//开启事务
TransactionStatus transaction=dataSourceTransactionManager.getTransaction(transactionDefinition);
//用户注册
Integer result=userService.insert(userName,password);
System.out.println("插入用户表,result: "+result);
//回滚事务
dataSourceTransactionManager.rollback(transaction);
//提交事务
dataSourceTransactionManager.commit(transaction);
return true;
}
}
🎈Spring 声明式事务 @Transactional
程序运行成功,事务正常提交@RestController @Slf4j @RequestMapping("/user3") public class UserController3 { @Autowired private UserService userService; @Transactional @RequestMapping("/register") public Boolean register(String userName, String password){ userService.insert(userName,password); return true; } }
再注册方法上加上@Transactional 注解即可。
程序运行错误,事务自动回滚:@RestController @Slf4j @RequestMapping("/user3") public class UserController3 { @Autowired private UserService userService; @Transactional @RequestMapping("/register") public Boolean register(String userName, String password){ userService.insert(userName,password); //强制程序抛出异常 int a = 10/0; return true; } }
数据库内容未更新。
@Transactional 作⽤@Transactional 可以⽤来修饰⽅法或类:
- • 修饰⽅法时: 只有修饰public ⽅法时才⽣效(修饰其他⽅法时不会报错, 也不⽣效)[推荐]
- • 修饰类时: 对 @Transactional 修饰的类中所有的 public ⽅法都⽣效.
⽅法/类被 @Transactional 注解修饰时, 在⽬标⽅法执⾏开始之前, 会⾃动开启事务, ⽅法执⾏结束之后, ⾃动提交事务.
- 如果在⽅法执⾏过程中, 出现异常, 且异常未被捕获, 就进⾏事务回滚操作.
——如上面直接强制程序抛出异常(也就是异常未被捕获,那么事务回滚)
- 如果异常被程序捕获, ⽅法就被认为是成功执⾏, 依然会提交事务.
@RestController @Slf4j @RequestMapping("/user3") public class UserController3 { @Autowired private UserService userService; @Transactional @RequestMapping("/register") public Boolean register(String userName, String password){ userService.insert(userName,password); log.info("用户注册成功"); //对异常进⾏捕获 try { //强制程序抛出异常 int a = 10/0; }catch (Exception e){ e.printStackTrace(); } return true; } }
事务未回滚,依旧提交事务。
如果程序中,出现错误,我们依旧需要事务进行回滚,不提交事务,该如何处理呢?
1. 重新抛出异常@RestController @Slf4j @RequestMapping("/user3") public class UserController3 { @Autowired private UserService userService; @Transactional @RequestMapping("/register") public Boolean register(String userName, String password){ userService.insert(userName,password); log.info("用户注册成功"); //对异常进⾏捕获 try { //强制程序抛出异常 int a = 10/0; }catch (Exception e){ throw e; } return true; } }
2. ⼿动回滚事务使⽤ TransactionAspectSupport.currentTransactionStatus() 得到当前的事务, 并使⽤ setRollbackOnly 设置 setRollbackOnly@RestController @Slf4j @RequestMapping("/user3") public class UserController3 { @Autowired private UserService userService; @Transactional @RequestMapping("/register") public Boolean register(String userName, String password){ userService.insert(userName,password); log.info("用户注册成功"); //对异常进⾏捕获 try { //强制程序抛出异常 int a = 10/0; }catch (Exception e){ TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return true; } }
🍭@Transactional 详解
通过上⾯的代码, 我们学习了 @Transactional 的基本使⽤. 接下来我们学习 @Transactional注解的使⽤细节.我们主要学习 @Transactional 注解当中的三个常⻅属性:
- 1. rollbackFor: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型
- 2. Isolation: 事务的隔离级别. 默认值为 Isolation.DEFAULT
- 3. propagation: 事务的传播机制. 默认值为 Propagation.REQUIRED
📝rollbackFor
@RestController
@Slf4j
@RequestMapping("/user3")
public class UserController3 {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/register")
public Boolean register(String userName, String password) throws IOException {
userService.insert(userName,password);
log.info("用户注册成功");
//对异常进⾏捕获
try {
//强制程序抛出异常
int a = 10/0;
}catch (Exception e){
throw new IOException();
}
return true;
}
}
@Transactional(rollbackFor = Exception.class)
@RestController
@Slf4j
@RequestMapping("/user3")
public class UserController3 {
@Autowired
private UserService userService;
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/register")
public Boolean register(String userName, String password) throws IOException {
userService.insert(userName,password);
log.info("用户注册成功");
//对异常进⾏捕获
try {
//强制程序抛出异常
int a = 10/0;
}catch (Exception e){
throw new IOException();
}
return true;
}
}
❗结论(异常捕获问题)
- • 在Spring的事务管理中,默认只在遇到运⾏时异常RuntimeException和Error时才会回滚.
- • 如果需要回滚指定类型的异常, 可以通过rollbackFor属性来指定. @Transactional(rollbackFor = Exception.class)
📝spring事务隔离级别
MySQL 事务隔离级别(回顾)SQL 标准定义了四种隔离级别, MySQL 全都⽀持. 这四种隔离级别分别是:
- 1. 读未提交(READ UNCOMMITTED): 读未提交, 也叫未提交读. 该隔离级别的事务可以看到其他事务中未提交的数据. 因为其他事务未提交的数据可能会发⽣回滚, 但是该隔离级别却可以读到, 我们把该级别读到的数据称之为脏数据, 这个问题称之为脏读.
- 2. 读提交(READ COMMITTED): 读已提交, 也叫提交读. 该隔离级别的事务能读取到已经提交事务的数据, 该隔离级别不会有脏读的问题.但由于在事务的执⾏中可以读取到其他事务提交的结果, 所以在不同时间的相同 SQL 查询可能会得到不同的结果, 这种现象叫做不可重复读
- 3. 可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也就可以确保同⼀事务多次查询的结果⼀致, 但是其他事务新插⼊的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别. ⽐如此级别的事务正在执⾏时, 另⼀个事务成功的插⼊了某条数据, 但因为它每次查询的结果都是⼀样的, 所以会导致查询不到这条数据, ⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因). 明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去, 这个现象叫幻读.
- 4. 串⾏化(SERIALIZABLE): 序列化, 事务最⾼隔离级别. 它会强制事务排序, 使之不会发⽣冲突, 从⽽解决了脏读, 不可重复读和幻读问题, 但因为执⾏效率低, 所以真正使⽤的场景并不多.
Spring 事务隔离级别Spring 中事务隔离级别有5 种:
- 1. 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
📝Spring 事务传播机制
⽐如有两个⽅法A, B都被 @Transactional 修饰, A⽅法调⽤B⽅法A⽅法运⾏时, 会开启⼀个事务. 当A调⽤B时, B⽅法本⾝也有事务, 此时B⽅法运⾏时, 是加⼊A的事务, 还是创建⼀个新的事务呢?这个就涉及到了事务的传播机制⽐如公司流程管理执⾏任务之前, 需要先写执⾏⽂档, 任务执⾏结束, 再写总结汇报此时A部⻔有⼀项⼯作, 需要B部⻔的⽀援, 此时B部⻔是直接使⽤A部⻔的⽂档, 还是新建⼀个⽂档呢?
Spring 事务传播机制有以下 7 种: 方法A调用方法B A->B
- 1. 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 .
1,4,7不存在事务创建事务
2,5 不存在事务以非事务运行
3,6 存在事务就抛出异常
✅Propagation.REQUIRED(事务之间互相影响)
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public Integer insert(String userName, String password) {
return userInfoMapper.insert(userName,password);
}
}
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public Integer insert(String userName, String op) {
Integer result= logInfoMapper.insertLog(userName,op);
return result;
}
}
@RequestMapping("/user2")
@RestController
@Slf4j
public class UserController2 {
@Autowired
private UserService userService;
@Autowired
private LogService logService;
@Transactional
@RequestMapping("/register")
public Boolean register(String userName, String password){
/**
* 用户表和日志表的插入,理应在service中完成
*/
Integer result=userService.insert(userName,password);
System.out.println("插入用户表,result: "+result);
//插入日志表
Integer logResult=logService.insert(userName,"用户注册");
System.out.println("插入日志表,result:"+logResult);
return true;
}
}
从上面看程序成功,事务提交,用户表和日志表都更新。
在LogService中加入异常,查看运行结果
我们观察运行结果事务回滚,数据库中两个表都没有更新,因为它们是处于同一个事务中。
运⾏程序, 发现数据库没有插⼊任何数据.流程描述:
- 1. register⽅法开始事务
- 2. ⽤⼾注册, 插⼊⼀条数据 (执⾏成功) (和register使⽤同⼀个事务)
- 3. 记录操作⽇志, 插⼊⼀条数据(出现异常, 执⾏失败) (和register使⽤同⼀个事务)
- 4. 因为步骤3出现异常, 事务回滚. 步骤2和3使⽤同⼀个事务, 所以步骤2的数据也回滚了.
所以看出required事务传播级别,不管是多少个方法,都共存在一个事务中只要存在事务,没有事务就创建事务。如果其中一个出现异常,那么都回滚,只有都执行成功,那么事务提交。
✅Propagation.REQUIRED_NEW(新建事务,互不影响)
当我们不希望事务之间相互影响时, 可以使⽤该传播⾏为
✅NEVER (不⽀持当前事务, 抛异常)
✅NESTED(嵌套事务)
2.三个事务中部分失败
👩🏻💻NESTED和REQUIRED有什么区别?
对比REQUIRED传播事务机制
- 整个事务如果全部执⾏成功, ⼆者的结果是⼀样的.
- 如果事务⼀部分执⾏成功, REQUIRED加⼊事务会导致整个事务全部回滚. NESTED嵌套事务可以实现局部回滚, 不会影响上⼀个⽅法中执⾏的结果.
嵌套事务之所以能够实现部分事务的回滚, 是因为事务中有⼀个保存点(savepoint)的概念, 嵌套事务进⼊之后相当于新建了⼀个保存点, ⽽滚回时只回滚到当前保存点REQUIRED 是加⼊到当前事务中, 并没有创建事务的保存点, 因此出现了回滚就是整个事务回滚, 这就是嵌套事务和加⼊事务的区别.
🚩总结
- 1. Spring中使⽤事务, 有两种⽅式: 编程式事务(⼿动操作)和声明式事务. 其中声明式事务使⽤较多,在⽅法上添加 @Transactional 就可以实现了
- 2. 通过 @Transactional(isolation = Isolation.SERIALIZABLE) 设置事务的隔离级别. Spring 中的事务隔离级别有 5 种
- 3. 通过 @Transactional(propagation = Propagation.REQUIRED) 设置事务的传播机制, Spring 中的 事务传播级别有 7 种, 重点关注 REQUIRED (默认值,公用一个事务,事务之间相互影响) 和 REQUIRES_NEW(几个方法就是几个事务,事务之间互不影响,相互独立)
事情一步一步的来。