注意事项
方法内部存在异常, 但被捕获(try-catch)时, 仍会提交事务
即方法执行完(无异常) → 自动提交事务
代码示例如下
@RestController @RequestMapping("/user") public class UserController { @RequestMapping("/insert") @Transactional public Integer insert(UserInfo userInfo) { // 非空校验 if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) { return 0; } int ret = userService.add(userInfo); System.out.println("insert : " + ret); try { int num = 2 / 0; } catch (Exception e) { System.out.println(e.getMessage()); } return ret; } }
针对上述情况, 想要回滚事务, 有 2 种解决方式
- 继续抛出异常
- 手动回滚事务(推荐)
继续抛出异常🍂
@RestController @RequestMapping("/user") public class UserController { @RequestMapping("/insert") @Transactional public Integer insert(UserInfo userInfo) { // 非空校验 if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) { return 0; } int ret = userService.add(userInfo); System.out.println("insert : " + ret); try { int num = 2 / 0; } catch (Exception e) { System.out.println(e.getMessage()); // 抛出异常 throw e; } return ret; } }
验证效果(抛出异常)
手动回滚事务🍂
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
@RestController @RequestMapping("/user") public class UserController { @RequestMapping("/insert") @Transactional public Integer insert(UserInfo userInfo) { // 非空校验 if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) { return 0; } int ret = userService.add(userInfo); System.out.println("insert : " + ret); try { int num = 2 / 0; } catch (Exception e) { System.out.println(e.getMessage()); // 手动回滚事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return ret; } }
验证效果(手动回滚事务)
@Transactional 的工作原理
@Transactional 是基于 AOP 实现, AOP 使用动态代理实现
目标对象实现了接口, 默认采用 JDK 的动态代理 / 目标对象未实现接口, 默认采用 CGLIB 的动态代理
@Transactional 在开始执行业务之前, 通过代理开启事务, 执行成功之后提交事务(执行期间遇到异常回滚事务)
🔎Spring—事务的隔离级别
事务有 4 大特性(ACID)
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
原子性🍂
定义
⼀个事务(Transaction)中的所有操作, 要么全部完成, 要么全部不完成, 不会结束在中间某个环节. 事务在执⾏过程中发⽣错误, 会被回滚(Rollback)到事务开始前的状态, 就像这个事务从来没有执⾏过⼀样
翻译
用户 A 给用户 B 转账
要么 A 成功转账给 B, B 收到转账的钱
要么 A 没能转账给 B, B 未收到转账的钱
不能出现 A 成功转账给 B, B 未收到钱等类似的情况
一致性🍂
定义
在事务开始之前和事务结束以后, 数据库的完整性没有被破坏. 这表示写⼊的资料必须完全符合所有的预设规则, 这包含资料的精确度, 串联性以及后续数据库可以⾃发性地完成预定的工作
翻译
用户 A 给用户 B 转账 520
A 账户被扣款 520, B 账户增加 520
不能出现 A 账户扣款 1000, B 账户增加 520 等类似的情况
隔离性🍂
定义
数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒, 隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不⼀致. 事务隔离分为不同级别, 包括读未提交(Read Uncommitted), 读已提交(Read Committed), 可重复读(Repeatable Read)和串行化(Serializable)
翻译(摘抄自网络)
将点餐过程理解为一个事务
制作 → 出餐 → 送餐 → 结算作为一个事务
不同的客户进行点餐是相互独立的, 并不会彼此影响 → 事物的隔离性
当一个事务影响其他事务时, 其他事务将会回滚
今日份的猪脚饭已经全部卖完, 再次点餐将会影响后续事务(制作 → 出餐 → 送餐 → 结算), 此时后续事务将会回滚
持久性🍂
定义
事务处理结束后, 对数据的修改就是永久的, 即便系统故障也不会丢失
翻译
用户 A 执行转账操作, 银行系统会开启事务, 将转账金额从 A 账户扣除, 将对应金额添加至 B 账户
当事务提交成功后, 系统会将更改持久化到数据库
即使系统发生故障, 数据库仍然能够恢复并保持转账操作的结果
MySQL—事务的隔离级别
MySQL—事务的隔离级别🍂
- READ UNCOMMITTED → 读未提交
- READ COMMITTED → 读已提交
- REPEATABLE READ → 可重复读(MySQL 默认的事务隔离级别)
- SERIALIZABLE → 串行化
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(READ UNCOMMITTED) | ✔ | ✔ | ✔ |
读已提交(READ COMMITTED) | ✘ | ✔ | ✔ |
可重复读(REPEATABLE READ) | ✘ | ✘ | ✔ |
串行化(SERIALIZABLE) | ✘ | ✘ | ✘ |
Spring—事务的隔离级别
Spring—事务的隔离级别🍂
- Isolation.DEFAULT → 以链接数据库的事务隔离级别为主
- Isolation.READ_UNCOMMITTED → 读未提交
- Isolation.READ_COMMITTED → 读已提交
- Isolation.REPEATABLE_READ → 可重复读
- Isolation.SERIALIZABLE → 串行化
Spring—设置事务的隔离级别
@Transactional(isolation = Isolation.DEFAULT) public void setIsolationLevel() { }
🔎Spring—事务的传播机制
事务的传播机制 → 事务的隔离级别 Plus 版
事务的隔离级别🍂
解决多个事务同时调用数据库的问题
事务的传播机制
解决一个事务在多个方法中传递的问题
Spring—事务传播机制的分类
Spring—事务传播机制的分类🍂
- Propagation.REQUIRED → 默认的事务传播级别. 表示如果当前存在事务, 则加入该事务 / 如果当前不存在事务, 则创建一个新的事务
- Propagation.SUPPORTS → 如果当前存在事务, 则加入该事务 / 如果当前不存在事务, 则以非事务方式运行
- Propagation.MANDATORY → 如果当前存在事务, 则加入该事务 / 如果当前不存在事务, 则抛出异常
- Propagation.REQUIRES_NEW → 表示创建一个新的事务. 如果当前存在事务, 则把当前事务挂起(强制必须有事务)
- Propagation.NOT_SUPPORTED → 以非事务方式运行, 如果当前存在事务, 则把当前事务挂起
- Propagation.NEVER → 以非事务方式运行, 如果当前存在事务, 则抛出异常
- Propagation.NESTED → 如果当前存在事务, 则创建一个事务作为当前事务的嵌套事务运行 / 如果当前不存在事务, 效果等同于 Propagation.REQUIRED
举个栗子🌰
对比加入事务与嵌套事务
UserController
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Autowired private LoginService loginService; @RequestMapping("/insert") @Transactional public Integer insert(UserInfo userInfo) { // 非空校验 if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) { return 0; } int ret = userService.add(userInfo); if(ret > 0) { loginService.add(); } return ret; } }
加入事务🍂
以@Transactional(propagation = Propagation.REQUIRED)
为例
UserService
@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional(propagation = Propagation.REQUIRED) public Integer add(UserInfo userInfo) { int ret = userMapper.add(userInfo); System.out.println("添加 : " + ret); return ret; } }
LoginService
@Service public class LoginService { @Transactional(propagation = Propagation.REQUIRED) public Integer add() { try { int num = 1 / 0; } catch (Exception e) { System.out.println(e.getMessage()); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return -1; } }
验证效果(执行期间出现异常 → 回滚全部事务)
嵌套事务🍂
以@Transactional(propagation = Propagation.
为例
UserService
@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional(propagation = Propagation.NESTED) public Integer add(UserInfo userInfo) { int ret = userMapper.add(userInfo); System.out.println("添加 : " + ret); return ret; } }
LoginService
@Service public class LoginService { @Transactional(propagation = Propagation.NESTED) public Integer add() { try { int num = 1 / 0; } catch (Exception e) { System.out.println(e.getMessage()); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return -1; } }
验证效果(执行期间出现异常 → 回滚部分事务)
总结加入事务与嵌套事务之间的区别
加⼊事务(REQUIRED) 和 嵌套事务(NESTED) 的区别
- 整个事务如果全部执行成功,结果均相同
- 如果事务执行期间失败了, 那么加入事务会将整个事务全部回滚;嵌套事务则会局部回滚,不会影响上⼀个方法中执行的结果
🌸🌸🌸完结撒花🌸🌸🌸