将一组操作封装成一个执行单元, 即这一组操作一同成功 / 一同失败
举个栗子🌰
未使用事务
滑稽老哥给女神转账 520
由于某种原因, 女神并未收到转账的 520, 而滑稽老哥却被扣款 520
使用事务
滑稽老哥给女神转账 520
由于某种原因, 女神并未收到转账的 520
因为使用事务, 所以滑稽老哥的钱也被重新打回账户上
(一同成功 / 一同失败)
Spring—事务的实现有 2 种方式
后续内容针对数据表 userinfo 进行演示🍂
-- 创建用户表 drop table if exists userinfo; create table userinfo( id int primary key auto_increment, username varchar(100) not null, password varchar(32) not null, photo varchar(500) default '', createtime timestamp default current_timestamp, updatetime timestamp default current_timestamp, `state` int default 1 ) default charset 'utf8mb4'; -- 添加一个用户信息 INSERT INTO `excnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES (1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);
Controller 层(UserController) → Service 层(UserService) → Mapper 层(UserMapper)🍂
UserController
代码在 Spring 编程式事务
UserService
@Service public class UserService { @Autowired private UserMapper userMapper; public Integer add(UserInfo userInfo) { return userMapper.add(userInfo); } }
UserMapper
@Mapper public interface UserMapper { int add(UserInfo userInfo); }
UserMapper 对应的 SQL 语句
insert into userinfo(username, password) values(#{username}, #{password})
UserInfo
@Data public class UserInfo { private Integer id; private String username; private String password; private String photo; private String createtime; private String updatetime; private Integer state; }
Spring 编程式事务与 MySQL 操作事务类似, 分为 3 个部分
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Autowired private DataSourceTransactionManager transactionManager; @Autowired private TransactionDefinition transactionDefinition; @RequestMapping("/add") public Integer add(UserInfo userInfo) { // 非空校验 if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) { return 0; } // 1. 开启事务 TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition); // 执行相关业务代码 int ret = userService.add(userInfo); System.out.println("add : " + ret); // 2. 提交事务 transactionManager.commit(transactionStatus); // 3. 回滚事务 transactionManager.rollback(transactionStatus); return ret; } }
验证效果(开启事务 + 提交事务)🍂
验证效果(开启事务 + 回滚事务)🍂
利用注解 @Transactional 实现 Spring 声明式事务
@Transactional 的特点
举个栗子🌰
方法执行期间出现异常 → 自动回滚事务
方法执行完(无异常) → 自动提交事务
参数 | 作用 |
---|---|
value | 配置多个事务管理器时, 可指定需要的事务管理器 |
transactionManager | 配置多个事务管理器时, 可指定需要的事务管理器 |
propagation | 事务的传播行为, 默认值为 Propagation.REQUIRED |
isolation | 事务的隔离级别, 默认值为 Isolation.DEFAULT |
timeout | 事务的超时时间, 默认值为 -1, 表示超时时间为无限大, 即事务将一直持续直到完成或手动回滚(如果超出设置的时间限制事务仍未完成, 则自动回滚事务) |
readOnly | 指定事务是否为只读, 默认值为 false(为了忽略不需要事务的方法, 例如读取数据, 设置 read-only 为 true) |
rollbackFor | 用于指定能够触发事务回滚的异常类型(可指定多个异常类型) |
rollbackForClassName | 用于指定能够触发事务回滚的异常类型(可指定多个异常类型) |
noRollbackFor | 抛出指定的异常类型, 不回滚事务(可指定多个异常类型) |
noRollbackForClassName | 抛出指定的异常类型, 不回滚事务(可指定多个异常类型) |
方法内部存在异常, 但被捕获(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 是基于 AOP 实现, AOP 使用动态代理实现
目标对象实现了接口, 默认采用 JDK 的动态代理 / 目标对象未实现接口, 默认采用 CGLIB 的动态代理
@Transactional 在开始执行业务之前, 通过代理开启事务, 执行成功之后提交事务(执行期间遇到异常回滚事务)
事务有 4 大特性(ACID)
原子性🍂
定义
⼀个事务(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—事务的隔离级别🍂
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(READ UNCOMMITTED) | ✔ | ✔ | ✔ |
读已提交(READ COMMITTED) | ✘ | ✔ | ✔ |
可重复读(REPEATABLE READ) | ✘ | ✘ | ✔ |
串行化(SERIALIZABLE) | ✘ | ✘ | ✘ |
Spring—事务的隔离级别🍂
@Transactional(isolation = Isolation.DEFAULT) public void setIsolationLevel() { }
事务的传播机制 → 事务的隔离级别 Plus 版
事务的隔离级别🍂
解决多个事务同时调用数据库的问题
事务的传播机制
解决一个事务在多个方法中传递的问题
Spring—事务传播机制的分类🍂
举个栗子🌰
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) 的区别
🌸🌸🌸完结撒花🌸🌸🌸