1. 事务回顾
1.1 什么是事务?
事务是一组操作的集合,是一个不可分割的操作。
事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败。
1.2 为什么需要事务
我们在进行程序开发时,也会有事务的需求。
比如转账操作:第一步:A账户-100元。
第二步:B账户+100元。
如果没有事务,第一步执行成功了,第二步执行失败了,那么A账户的100元就平白无故消失了。如果使用事务就可以解决这个问题,让这一组操作要么一起成功,要么一起失败。
1.3 事务的操作
事务操作的主要有三步:
-
开启事务start transaction/begin(一组操作前开启事务)。
-
提交事务:commit(这组操作全部成功,提交事务)。
-
回滚事务:rollback(这组操作中间任何一个操作出现异常,回滚事务)。
sql
-- 开启事务
start transaction;
-- 提交事务
commit;
-- 回滚事务
rollback;
2. Spring中事务的实现
Spring对事务也进行了实现。
Spring中的事务操作分为两类:
-
编程式事务:(手动写代码操作事务)。
-
声明式事务:(利用注解自动开启和提交事务)。
示例:用户注册,注册时在日志中插入一条操作记录。
数据准备:
sql
-- 创建数据库
DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;
-- 使用该数据库
USE trans_test;
-- 用户表
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 CHARSET = 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()
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
代码准备:
1. 创建项目 spring-trans,引入Spring Web,Mybatis,mysql等依赖。
2. 配置文件
java
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
3. 实体类
java
@Data
public class UserInfo {
private Integer id;
private String userName;
private String password;
private Date createTime;
private Date updateTime;
}
java
@Data
public class LogInfo {
private Integer id;
private String userName;
private String op;
private Date createTime;
private Date updateTime;
}
4. Mapper
java
@Mapper
public interface LogInfoMapper {
@Insert("insert into log_info(`user_name`,`op`)values(#{name},#{op})")
Integer insertLog(String name,String op);
}
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info(`user_name`,`password`)values(#{name},#{password})")
Integer insert(String username,String password);
}
5. Service
java
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
public void insertLog(String name,String op){
//记录⽤⼾操作
logInfoMapper.insertLog(name,"⽤⼾注册");
}
}
java
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public void registryUser(String name,String password){
userInfoMapper.insert(name,password);
}
}
6. Controller
java
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String name,String password){
userService.registryUser(name,password);
return "注册成功";
}
}
3. @Transactional详解
@Transactional注解当中的三个常见属性:
-
rollbackFor:异常回滚属性,指定能够触发事务回滚的异常类型,可以指定多个异常类型。
-
Isolation:事务的隔离级别。默认值为Isoation.DEFAULT
3.propagation:事务的传播机制。默认值为Propagation.REQUIRED
3.1 rollbackFor
@Transactional 默认只在遇到运行时异常和Error时才会回滚,非运行时异常不会滚。即Exception的子类中,除了RuntimeException及其子类。

3.2 事务的隔离级别
3.2.1 MySQL事务的隔离级别
读未提交、读已提交、可重复读、串行化

3.2.2 Spring事务隔离级别
Spring中事务的隔离级别有5种:
-
solation.DEFAULT:以连接的数据库的事务隔离级别为主。
-
Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准中READ UNCOMMITTED。
-
Isolation.READ_COMMITTED:读已提交,对应SQL标准中READ COMMITTED。
-
Isolation.REPEATABLE_READ:可重复读,对应SQL标准中 REPEATABLE READ。
-
Isolation.SERIALIZABLE:串行化,对应SQL标准中SERIALIZABLE。
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:默认的事务传播级别,如果当前存在事务,则加入该事务。如果当前没有事务,则创建一个新的事务。
-
Propagation.SUPPORTS:如果当前存在事务,则加入该事务。如果当前没有事务,则以非事务的方式继续运行。
-
Propagation.MANDATORY:强制性,如果当前存在事务,则加入该事务。
-
Propagation.REQUIRES_NEW:创建一个新的事务。如果当前存在事务,则把当前事务挂起。不管外部方法是否开启事务,它都会新开启自己的事务,且开启的事务互相独立,互不干扰。
-
Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
-
Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
-
Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED 。
3.3.3 Spring事务传播机制的使用和演示
演示两个:REQUIRED、REQUIRES_NEW
3.3.3.1 REQUIRED(加入事务)
看下面代码如何实现:
-
用户注册,插入一条数据
-
记录操作日志,插入一条数据(出现异常)
java
@RestController
@RequestMapping("/propaga")
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";
}
}
java
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void registryUser(String name, String password) {
userInfoMapper.insert(name, password);
}
}
java
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void insertLog(String name, String op) {
int a = 10 / 0; // 💥 必然抛出 ArithmeticException(RuntimeException)
logInfoMapper.insertLog(name, "用户注册");
}
}
运行程序,发现数据库没有插入任何数据。
流程描述:
-
p1方法开启事务
-
用户注册,插入一条数据(执行成功)
-
记录操作日志,插入一条数据(出现异常,执行失败)(和p1使用同一个事务)
-
因为步骤3出现异常,事务回滚,步骤2和3使用同一个事务,所以步骤2的数据也回滚了
3.3.3.2 REQUIRES_NEW(新建事务)
java
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void registryUser(String name, String password) {
userInfoMapper.insert(name, password);
}
}
java
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertLog(String name, String op) {
int a = 10 / 0; // 💥 抛出 ArithmeticException(RuntimeException)
logInfoMapper.insertLog(name, "用户注册");
}
运行程序,发现用户数据插入成功了,日志表数据插入失败。
LogService方法中的事务不影响UserService中的事务。
4. 总结
-
Spring中使用事务,有两种方式:编程式事务(手动操作)和声明式事务。其中声明式事务使用较多,在方法上添加@Transactional 就可以实现了。
-
通过@Transactional(isolation = Isolation.SERIALIZABLE) 设置事务的隔离级别。Spring中的事务隔离级别有5种。
-
通过@Transactional(propagation =Propagation.REQUIRED)设置事务的传播机制,Spring中的 事务传播级别有7种,重点关注REQUIRED (默认值)和REQUIRES_NEW。