文章目录
-
- 一、事务
-
- [1.1 什么是事务](#1.1 什么是事务)
- [1.2 为什么需要事务?](#1.2 为什么需要事务?)
- [1.3 事务的操作](#1.3 事务的操作)
- [二、Spring 中事务的实现](#二、Spring 中事务的实现)
-
- [2.1 Spring 编程式事务](#2.1 Spring 编程式事务)
- [2.2 Spring 声明式事务 @Transactional](#2.2 Spring 声明式事务 @Transactional)
-
- [2.2.1 @Transactional 作用](#2.2.1 @Transactional 作用)
- [2.3 @Transactional 详解](#2.3 @Transactional 详解)
-
- [2.3.1 rollbackFor](#2.3.1 rollbackFor)

一、事务
1.1 什么是事务
事务是一组操作的集合,是一个不可分割的操作。
事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败。
1.2 为什么需要事务?
在程序开发中,事务的核心价值是保证一组关联操作的原子性(要么全部成功,要么全部失败),避免因部分操作成功、部分操作失败导致的数据不一致问题。
- 解决 "部分操作成功" 导致的数据异常:在多步关联操作中,若没有事务控制,某一步执行失败后,已执行的步骤无法回滚,会造成数据逻辑错误。
- 典型场景:转账操作核心逻辑是 "A 账户扣 100 元 + B 账户加 100 元"。若没有事务:A 账户扣钱成功后,B 账户加钱操作因网络波动、代码异常等失败,会导致 A 账户的 100 元凭空消失,资金数据不一致。
若有事务:两步操作被视为一个整体,只要其中一步失败,整个事务回滚,A 账户的扣款会撤销,数据恢复到操作前状态。
- 保证业务逻辑的完整性:很多业务场景中,多个数据操作是 "强关联" 的,缺少任何一步都会导致业务逻辑断裂。
- 应对并发场景下的数据一致性问题:在多用户并发操作同一数据时,事务结合隔离级别(如读已提交、可重复读),可避免脏读、不可重复读、幻读等问题,保证每个用户看到的数据是可靠的。
1.3 事务的操作
事务的操作主要有三步:
- 开启事务:
start transaction/ begin(一组操作前开启事务) - 提交事务:
commit(这组操作全部成功,提交事务) - 回滚事务:
rollback(这组操作中间任何一个操作出现异常,回滚事务)
二、Spring 中事务的实现
Spring 中的事务操作分为两类:
- 编程式事务(手动写代码操作事务)。
- 声明式事务(利用注解自动开启和提交事务)。
2.1 Spring 编程式事务
Spring 手动操作事务和上面 MySQL 操作事务类似,有 3 个重要操作步骤:
- 开启事务(获取事务)
- 提交事务
- 回滚事务
SpringBoot 内置了两个对象:
- DataSourceTransactionManager 事务管理器:用来获取事务(开启事务)、提交或回滚事务。
- TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得一个事务 TransactionStatus。
代码实现:
java
@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 "注册成功";
}
}
运行后观察数据库的结果,数据插入成功。
观察事务回滚: dataSourceTransactionManager.rollback(transactionStatus);
运行后观察数据库,虽然程序返回"注册成功",但数据库并没有新增数据。
2.2 Spring 声明式事务 @Transactional
声明式事务的实现很简单,两步操作:
- 添加依赖
xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
- 在需要事务的方法上添加 @Transactional 注解就可以实现了。无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务。
代码实现:
java
@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 "注册成功";
}
}
运行程序,发现数据插入成功。
修改程序,使之出现异常
java
@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 "注册成功";
}
}
运行程序:发现虽然日志显示数据插入成功,但数据库却没有新增数据,事务进行了回滚。
一般会在业务逻辑层当中来控制事务,因为在业务逻辑层当中,一个业务功能可能会包含多个数据访问的操作。在业务逻辑层来控制事务,我们就可以将多个数据访问操作控制在一个事务范围内。
2.2.1 @Transactional 作用
@Transactional 可以用来修饰方法或类:
- 修饰方法时:只有修饰public 方法时才生效(修饰其他方法时不会报错,也不生效)[推荐]
- 修饰类时:对@Transactional 修饰的类中所有的 public 方法都生效。
方法/类被 @Transactional 注解修饰时,在目标方法执行开始之前,会自动开启事务,方法执行结束之后,自动提交事务。如果在方法执行过程中,出现异常,且异常未被捕获,就进行事务回滚操作。如果异常被程序捕获,方法就被认为是成功执行,依然会提交事务。
示例:对异常进行捕获
java
@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("用户数据插入成功");
//对异常进行捕获
try {
//强制程序抛出异常
int a = 10 / 0;
} catch (Exception e) {
e.printStackTrace();
}
return "注册成功";
}
}
运行程序,发现虽然程序出错了,但是由于异常被捕获了,所以事务依然得到了提交。
如果需要事务进行回滚,有以下两种方式:
- 重新抛出异常
java
@Slf4j
@RequestMapping("/trans")
@RestController
public class TransactionalController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/registry")
public String registry(String name, String password) throws Exception {
//用户注册
userService.registryUser(name, password);
log.info("用户数据插入成功");
//对异常进行捕获
try {
//强制程序抛出异常
int a = 10 / 0;
} catch (Exception e) {
//将异常重新抛出去
throw e;
}
return "注册成功";
}
}
- 手动回滚事务
使用 TransactionAspectSupport.currentTransactionStatus() 得到当前的事务,并使用 setRollbackOnly 设置回滚。
java
@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("用户数据插入成功");
//对异常进行捕获
try {
//强制程序抛出异常
int a = 10 / 0;
} catch (Exception e) {
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return "注册成功";
}
}
2.3 @Transactional 详解
@Transactional 注解当中的三个常见属性:
- rollbackFor:异常回滚属性。指定能够触发事务回滚的异常类型。可以指定多个异常类型
- Isolation:事务的隔离级别。默认值为Isolation.DEFAULT
- propagation:事务的传播机制。默认值为 Propagation.REQUIRED
2.3.1 rollbackFor
@Transactional 默认只在遇到运行时异常和Error时才会回滚,非运行时异常不回滚。即 Exception的子类中,除了RuntimeException及其子类。
如果需要所有异常都回滚,需要来配置@Transactional 注解当中的rollbackFor 属性,通过rollbackFor 这个属性指定出现何种异常类型时事务进行回滚。
java
@Slf4j
@RequestMapping("/trans")
@RestController
public class TransactionalController {
@Autowired
private UserService userService;
@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属性来指定。
至于Transactional 的另外两个属性:事务的隔离级别、事务的传播机制会在后面的章节进行介绍。