spring的事务
一、事务介绍
事务:数据库中多个操作合并在一起形成的操作序列,事务特征(ACID)
作用:
-
当数据库操作序列中个别操作失败时,提供一种方式使数据库状态恢复到正常状态(A),保障数据库即使在异常状态下仍能保持数据一致性(C)(要么操作前状态,要么操作后状态)
-
当出现并发访问数据库时,在多个访问间进行相互隔离,防止并发访问操作结果互相干扰(I)
Spring 事务一般加到业务层,对应着业务的操作,Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring 是无法提供事务功能的,Spring 只提供统一事务管理接口
Spring 在事务开始时,根据当前环境中设置的隔离级别,调整数据库隔离级别,由此保持一致。程序是否支持事务首先取决于数据库 ,比如 MySQL ,如果是 Innodb 引擎,是支持事务的;如果 MySQL 使用 MyISAM 引擎,那从根上就是不支持事务的
保证原子性:
-
要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚
-
在 MySQL 中,恢复机制是通过回滚日志(undo log) 实现,所有事务进行的修改都会先先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,直接利用回滚日志中的信息将数据回滚到修改之前的样子即可
-
回滚日志会先于数据持久化到磁盘上,这样保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务
二、隔离级别
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
-
TransactionDefinition.isolation_default:使用后端数据库默认的隔离级别,MySQL 默认采用的 repeatable_read 隔离级别,Oracle 默认采用的 READ_COMMITTED隔离级别.
-
TransactionDefinition.isolation_read_uncommitted:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
-
TransactionDefinition.isolation_read_committed:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
-
TransactionDefinition.isolation_repeatable_read:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
-
TransactionDefinition.isolation_serializable:最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)
三、七种事务传播行为
Spring事务传播行为实现原理
Spring的事务信息是存在ThreadLoacl中的,所以一个线程永远只能有一个事物
-
融入:当传播行为是融入外部事务则拿到ThreadLocal中的Connection,共享一个数据连接共同提交、回滚
-
创建新事务:当传播行为是创建新事务,会将嵌套事务存入ThreadLocal,再将外部暂存起来;当嵌套事务提交、回滚后,会将暂存的事务信息恢复到TheadLocal中
spring的七种事务传播行为
以下事务传播属性都是打在B方法上的事务注解
-
Propagation.required:spring默认的事务传播行为,A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己开启一个新事务
-
Propagation.supports:A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己使用非事务方式执行
-
Propagation.mandatory:只能在存在事务的方法中被调用,A方法调用B方法,如果A方法没事务,则B方法会抛出异常
-
Propagation.requires_new:A方法调用B方法,如果A方法有事务,则B方法把A方法的事务挂起,B方法自己重新开启一个新事务
-
Propagation.not_supported:A方法调用B方法,如果A方法有事务,则B方法挂起A方法中的事务中,否则B方法自己使用非事务方式执行
-
Propagation.never:不支持事务,A方法调用B方法,如果A方法有事务,则B方法会抛出异常
-
Propagation.nested:同 Propagation.required,不过此传播属性还可以, 保存状态节点,从而避免所有嵌套事务都回滚
四、实战
1、Propagation.REQUIRED
spring 默认的事务传播属性,A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己开启一个新事务
A方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 调用B接口的insertTest方法事务方法
BTestService.insertTest(test);
return i;
}
我们再来看下B方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 抛出异常
int a = 1 / 0 ;
return i;
}
假如说,我代码这么写的话,那么这个数据库里最终是会有什么数据呢?
@PostMapping("/update")
public Object updateTest(@RequestBody Test updateVO){
Integer flag = ATestService.updateTest(updateVO);
return flag;
}
原数据库内容

postman来一发看看

可以看到控制台的结果是这样的,他们共用一个事务(sqlSession是一样的)

此时数据库的内容也并没有发生变化,说明A,B接口都回滚了

场景一:如果B方法抛出的异常被 A方法try catch 捕获了,那么A方法的操作还会回滚吗?
答案是:会回滚
来看下测试代码,我们在A方法中添加了捕获B方法抛出异常的代码
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
try {
// 调用insertTest方法事务方法
BTestService.insertTest(test);
}catch (Exception e){
System.out.println("A方法补获了异常"+e.getMessage());
}
return i;
}
再次来一发postman,控制台输出测试结果如下

场景二:如果 A没有捕获,B方法自己捕获了异常 ,那么事务还会回滚吗 ?
答案是:不会
把B方法的代码改一下
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = 0;
try {
i = testMapper.insert(updateVO);
// 抛出异常
int a = 1 / 0 ;
}catch (Exception e){
System.out.println("B方法补获了异常"+e.getMessage());
}
return i;
}
同时把A方法的捕获异常去掉
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 调用insertTest方法事务方法
BTestService.insertTest(test);
return i;
}
这个时候的结果是

数据库的数据是

场景一、场景二总结
A方法捕获和B方法捕获有什么区别吗(指捕获异常)
-
区别就是,A方法捕获异常的话, B方法的事务注解会感知到异常的发生,从而回滚 ;
-
而B方法自己捕获
-
B方法,捕获异常并处理,不重新抛出;事务不会回滚,因为没有未处理的异常
-
B方法,捕获异常并重新抛出 RuntimeException;事务会回滚,因为 RuntimeException 是未检查异常,Spring默认会回滚事务
-
B方法,捕获异常并重新抛出受检异常(checked exception);事务不会回滚,除非在事务配置中显式指定了对受检异常的回滚规则
-
2、Propagation.supports
我们把B接口的事务传播属性换成 Propagation.supports
@Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 抛出异常
int a = 1 / 0 ;
return i;
}
A方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 调用insertTest方法事务方法
BTestService.insertTest(test);
return i;
}
测试结果是

数据库的值也没有被改变 , 所以两个操作都被回滚了 那我们把A方法的事务注解去掉后再看一下
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 调用insertTest方法事务方法
BTestService.insertTest(test);
return i;
}
测试结果是

数据库的值是

由此可见,两个操作都没有被回滚,B方法是以非事务方式进行的操作
3、Propagation.mandatory
只能在存在事务的方法中被调用,A方法调用B方法,如果A方法没事务,则B方法会抛出异常
A方法如下
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 调用insertTest方法事务方法
BTestService.insertTest(test);
return i;
}
B方法如下
@Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 抛出异常
int a = 1 / 0 ;
return i;
}

数据库的值也没有变,由此可见,B方法的事务注解为 Propagation.mandatory 当A方法没事务时,则直接报错。
4、Propagation.requires_new
A方法调用B方法,如果A方法有事务,则B方法把A方法的事务挂起,B方法自己重新开启一个新事务
A方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 调用insertTest方法事务方法
BTestService.insertTest(test);
return i;
}
B方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 抛出异常
int a = 1 / 0 ;
return i;
}
结果是

其中可以发现 两个方法的 DefaultSqlSession 不一样,那么就表明,这两个不是一个事务,所以就是,当A方法存在事务的时候,B方法将其挂起并且重新开启一个新的事务
-
方法抛出了异常,那么A方法没有捕获的话,则A,B方法都会回滚
-
A方法捕获了异常,则A方法不回滚
还是那句话,如果在方法内捕获了异常,则此方法上的事务注解就感知不到这个异常的存在了,那么此方法的操作就不会回滚
5、Propagation.not_suppported
A方法调用B方法,如果A方法有事务,则B方法挂起A方法中的事务,并且B方法自己以非事务方式执行
A方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 调用insertTest方法事务方法
BTestService.insertTest(test);
return i;
}
B方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 抛出异常
int a = 1 / 0 ;
return i;
}
测试结果是

数据库的结果是

由于B方法的事务传播行为是 Propagation.not_suppported 则会挂起A方法的事务,B方法以非事务情况操作(所以报错也不回滚),异常跑到了A方法内,A方法是有事务的,当被挂起了,
所以A方法不会回滚,所以就A方法的数据插入成功
6、Propagation.never
不支持事务,A方法调用B方法,如果A方法有事务,则B方法会抛出异常
A方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 调用insertTest方法事务方法
BTestService.insertTest(test);
return i;
}
B方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NEVER)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 抛出异常
int a = 1 / 0 ;
return i;
}
结果是

数据库也没有被改变, 可见,当A方法有事务的情况下调用B接口,直接报错
7、Propagation.nested
Propagation.required,不过此传播属性还可以, 保存状态节点,从而避免所有嵌套事务都回滚
A方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
try {
// 调用insertTest方法事务方法
BTestService.insertTest(test);
}catch (Exception e){
System.out.println("A方法补获了异常"+e.getMessage());
}
return i;
}
B方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 抛出异常
int a = 1 / 0 ;
return i;
}
结果是

数据库的变化如下

A方法的操作没有回滚,B操作的回滚了,这就是因为“savePoint”安全点,在进行B方法操作时,当前的状态(A方法已经操作完了)被保存至安全点,B方法失败的话,回滚只会回滚到这个安全点

浙公网安备 33010602011771号