1. 事务(不再介绍,详看初阶笔记)
什么是事务
为什么需要事务
事务的操作:
- 开启事starttransaction/begin(⼀组操作前开启事务)
- 提交事务:commit(这组操作全部成功,提交事务)
- 回滚事务:rollback(这组操作中间任何⼀个操作出现异常,回滚事务)
2. 事务的实现
Spring中的事务操作分为两类:
- 编程式事务(⼿动写代码操作事务).
- 声明式事务(利⽤注解⾃动开启和提交事务).
需求:⽤⼾注册,注册时在⽇志表中插⼊⼀条操作记录
准备: 数据库:user_info表,log_info表
实体类:
@Data
public class LogInfo {
private Integer id;
private String op;
private String userName;
private Date createTime;
private Date updateTime;
}
@Data
public class UserInfo {
private Integer id;
private String password;
private String userName;
private Date createTime;
private Date updateTime;
}
service:
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional
public Integer insertLog(String userName,String op){
logInfoMapper.insert(userName,op);
return 1;
}
}
@Service
public class UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public Integer insertUser(String userName,String password){
return userInfoMapper.insert(userName,password);
};
}
mapper:
@Mapper
public interface LogInfoMapper {
@Insert("insert into log_info (user_Name,op) values (#{userName},#{op})")
Integer insert(@Param("userName") String userName, @Param("op") String op);
}
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info (user_name,password) values(#{userName},#{password})")
Integer insert(@Param("userName") String userName, @Param("password") String password);
}
2.1 Spring编程式事务(了解)
Spring⼿动操作事务和MySQL操作事务类似,有3个重要操作步骤:
- 开启事务
- 提交事务
- 回滚事务
下面看一段代码:
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
//JDBC事务管理器
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
//定义事务属性
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserInfoService userInfoService;
@RequestMapping("/registry")
public String registry(String userName,String password){
//开启事务
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
Integer result = userInfoService.insertUser(userName,password);
log.info("用户插入成功,result:{}",result);
//回滚事务
//dataSourceTransactionManager.rollback(transaction);
//提交事务
dataSourceTransactionManager.commit(transaction);
return "注册成功";
}
}
观察事务提交和回滚时日志的区别:
提交:
回滚:
虽然回滚时,程序返回"注册成功",但是数据库并没有插入数据
SpringBoot 内置了两个对象:
- DataSourceTransactionManager:事务管理器.⽤来获取事务(开启事务),提交或回滚事务
- TransactionDefinition:是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从⽽获得⼀个事务TransactionStatus
2.2 Spring声明式事务@Transactional
2.2.1 操作
声明式事务的实现很简单,只需要在需要事务的⽅法上添加 @Transactional 注解就可以实现了.
⽆需⼿动开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了没有处理的异常会⾃动回滚事务.
看下面代码实现:
数据插入成功
修改程序,使之出现异常:
数据库却没有新增数据,事务进⾏了回滚.
2.2.2 @Transactional 作⽤
@Transactional 可以⽤来修饰⽅法或类:
- 修饰⽅法时:只有修饰public⽅法时才⽣效(修饰其他⽅法时不会报错,也不⽣效)[推荐]
- 修饰类时:对 @Transactional 修饰的类中所有的public⽅法都⽣效.
⽅法/类被@Transactional 注解修饰时,在⽬标⽅法执⾏开始之前,会⾃动开启事务,⽅法执⾏结束之后,⾃动提交事务.
如果在⽅法执⾏过程中,出现异常,且异常未被捕获,就进⾏事务回滚操作.
如果异常被程序捕获,⽅法就被认为是成功执⾏,依然会提交事务.
下面对异常进行捕获:
运⾏程序,虽然程序出错了,但是由于异常被捕获了,所以事务依然得到了提交.
2.2.3 回滚操作
如果需要事务进⾏回滚,有以下两种⽅式:
1. 重新抛出异常
2. 手动回滚事务
使⽤ TransactionAspectSupport.currentTransactionStatus() 得到当前的事务,并使⽤ setRollbackOnly设置
3. @Transactional 详解
三个常见属性:
- rollbackFor: 异常回滚属性.指定能够触发事务回滚的异常类型.可以指定多个异常类型
- Isolation: 事务的隔离级别.默认值为 Isolation.DEFAULT
- propagation: 事务的传播机制.默认值为 Propagation.REQUIRED
3.1 rollbackFor
@Transactional 默认只在遇到运⾏时异常和Error时才会回滚,⾮运⾏时异常不回滚.即 Exception的⼦类中,除了RuntimeException及其⼦类.
看下面代码:
运行时异常:(回滚):
非运行时异常(不回滚) :
虽然程序抛出了异常,但是事务依然进⾏了提交.
如果我们需要所有异常都回滚,需要来配置@Transactional 注解当中的rollbackFor 这个属性
通过这个属性指定出现何种异常类型时事务进⾏回滚.
结论:
- 在Spring的事务管理中,默认只在遇到运⾏时异常RuntimeException和Error时才会回滚.
- 如果需要回滚指定类型的异常,可以通过rollbackFor属性来指定.
3.2 事务隔离级别
3.2.1 MySQL事务隔离级别
简单回顾:
读未提交,读提交,可重复读,串行化
3.2.2 Spring 事务隔离级别
- Isolation.DEFAULT :以连接的数据库的事务隔离级别为主.
- Isolation.READ_UNCOMMITTED :读未提交,对应SQL标准中READ_UNCOMMITTED
- Isolation.READ UNCOMMITTED :读已提交,对应SQL标准中 READ COMMITTED
- Isolation.REPEATABLE_READ :可重复读,对应SQL标准中 REPEATABLE READ
- Isolation.SERIALIZABLE 串行化,对应SQL标准中SERIALIZABLE
Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置
3.3 Spring事务传播机制(了解)
3.3.1 什么是事务传播机制
多个事务⽅法存在调⽤关系时,事务是如何在这些⽅法间进⾏传播的
⽐如有两个⽅法A,B都被 @Transactional 修饰, A⽅法调⽤B⽅法 A⽅法运⾏时,会开启⼀个事务.
当A调⽤B时,B⽅法本⾝也有事务,此时B⽅法运⾏时,是加⼊A的事务,还 是创建⼀个新的事务呢? 这个就涉及到了事务的传播机制.
事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题
而事务传播机制解决的是⼀个事务在多个节点(⽅法)中传递的问题
3.3.2 事务的传播机制有哪些
机制 | 意义 | 举例 |
Propagation.REQUIRED(默认) | 如果当前存在事务,则加⼊该事务.如果当前没有事务,则创建⼀个新的事务 | 需要房⼦.如果你有房,我们就⼀起住,如果你没房,我们就⼀起买房 |
Propagation.SUPPORTS | :如果当前存在事务,则加⼊该事务.如果当前没有事务,则以⾮事务的 ⽅式继续运⾏ | 可以有房⼦.如果你有房,那就⼀起住.如果没房,那就租房 |
Propagation.MANDATORY | 强制性.如果当前存在事务,则加⼊该事务.如果当前没有事务,则 抛出异常. | 必须有房⼦.要求必须有房,如果没房就不结婚 |
Propagation.REQUIRES_NEW | 创建⼀个新的事务.如果当前存在事务,则把当前事务挂起 | 必须买新房.不管你有没有房,必须要两个⼈⼀起买房.即使有房也不住 |
Propagation.NEVER | 以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常 | 不能有房⼦ |
Propagation.NOT_SUPPORTED | 以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起 | 不需要房.不管你有没有房,我都不住,必须租房 |
Propagation.NESTED | 如果如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏. | 如果你没房,就⼀起买房.如果你有房,我们就以房⼦为根据地,做点下 ⽣意. |
3.3.3 Spring 事务传播机制使用和各种场景演示
这里演示三个:
- REQUIRED(默认值)
- REQUIRES_NEW
- NESTED
3.3.3.1 REQUIRED(加⼊事务)
@RestController
@RequestMapping("/proga")
@Slf4j
public class ProController {
@Autowired
private UserInfoService userInfoService;
@Autowired
private LogInfoService logInfoService;
@Transactional
@RequestMapping("/p1")
public String registry(String userName,String password){
Integer result = userInfoService.insertUser(userName,password);
Integer result2 = logInfoService.insertLog(userName,"用户自行注册");
return "注册成功";
}
}
对应的UserService和LogService都添加上@Transactional(propagation = Propagation. REQUIRED)
看第一种情况:
第二种:没有错误
总结:
事务全部成功时,都成功
一个失败,都失败
流程描述:
- p1⽅法开始事务
- ⽤⼾注册,插⼊⼀条数据(执⾏成功)(和p1使⽤同⼀个事务)
- 记录操作⽇志,插⼊⼀条数据(出现异常,执⾏失败)(和p1使⽤同⼀个事务)
- 因为步骤3出现异常,事务回滚.步骤2和3使⽤同⼀个事务,所以步骤2的数据也回滚了
3.3.3.2 REQUIRES_NEW(新建事务)
3.3.3.2 REQUIRES_NESTED(嵌套事务)
总结:
事务全部成功时,都成功
一个失败,都失败
流程:
- Controller 中p1⽅法开始事务 UserService
- ⽤⼾注册,插⼊⼀条数据(嵌套p1事务)
- LogService 记录操作⽇志,插⼊⼀条数据(出现异常,执⾏失败)(嵌套p1事务,回滚当前事务,数据添加失败)
- 由于是嵌套事务, LogService 出现异常之后,往上找调⽤它的⽅法和事务,所以⽤⼾注册也失败了.
- 最终结果是两个数据都没有添加
p1事务可以认为是⽗事务,嵌套事务是⼦事务.⽗事务出现异常,⼦事务也会回滚,⼦事务出现异常,如 果不进⾏处理,也会导致⽗事务回滚.
3.3.3.4 REQUIRED与NESTED区别
NESTED可以实现局部回滚
重新运⾏程序,发现⽤⼾表数据添加成功,⽇志表添加失败.