文章目录
相关文章
spring 的事务(transaction) 一 基础概念介绍
spring 的事务(transaction) 二 陷阱
spring 的事务(transaction) 三 try catch对事务的影响
spring 的事务(transaction) 四 嵌套事务PROPAGATION_NESTED
概述
spring事务的原理是什么?
-
首先mysql这样的数据库本身是支持事务的,有不同的事务隔离级别,事务分为手动开启事务和自动开启事务,参见
【mysql】MYSQL事务的开启与提交命令答疑
,通过底层的支持,可以实现多条sql 原子化,要么都执行,要么都不执行 - spring事务采用注解生成代理对象,把默认的自动开启事务变为手动开启,这样 多条sql语句都执行完后,才会提交事务
1. 什么是嵌套事务PROPAGATION_NESTED
我们回顾下
spring 的事务(transaction) 一 基础概念介绍
中的PROPAGATION_NESTED概念。
PROPAGATION_NESTED:
嵌套事务呈现父子事务概念,二者之间是有关联的,
核心思想就是子事务不会独立提交,而是取决于父事务,当父事务提交,那么子事务才会随之提交;如果父事务回滚,那么子事务也回滚。
与此相反,PROPAGATION_REQUIRES_NEW的内层事务,会立即提交,与外层毫无关联。
但是子事务又有自己的特性,那 就是可以独立进行回滚,不会引发父事务整体的回滚(当然需要try catch子事务,避免异常传递至父层事务,如果没有,则也会引发父事务整体回滚)。
这个特性比较有意思,虽然不能独立提交,但是可以独立回滚,因此,如果存在ABC 三个子事务,那么每个子事务都可以独立回滚,子事务类似一个游戏中的保存点,假设某个时间点,创建了一个保存点A,角色有10发子弹,主线继续发生时,对应执行某个子事务内的逻辑,如果游戏角色打了4发子弹,剩余6发子弹时挂了,点击返回上一个保存点,可以重新玩一次,此时该角色又是10发子弹,对应的就是子事务发生异常,子事务回滚到事务执行之前的那个点,放佛从来没有执行过该子事务一样,数据库的数据也不会发生变更。更重要的是,游戏角色仅会返回到某个进度的保存点,而不是返回到游戏的开始点,否则进度丢了,都想骂人了。
子事务可以独立回滚,也可以通过传递异常,让父事务也回滚,根源在于用户策略,在父事务通过try catch 对子事务进行包裹,灵活策略;
该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。
2. PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别
PROPAGATION_NESTED 和PROPAGATION_REQUIRES_NEW的区别:
PROPAGATION_REQUIRES_NEW内层事务执行完就立即提交。
PROPAGATION_NESTED 子事务执行完,不会立即提交,而是等待外层事务完成后一起提交。
2.1 验证PROPAGATION_REQUIRES_NEW内层事务执行完就立即提交
我们复用
spring 的事务(transaction) 三 try catch对事务的影响
中的代码,在 “1. 非异常用例”章节中,会分别向2个表里面各自插入一条数据,那么
我们通过断点方式,来查看独立事务的提交。
我们在UserServiceImpl中打断点,为了打断点,我们需要增加一行打印,作用是在内层事务执行后打断点:
System.out.println("addOrder finished");
完整方法代码:
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void addUser(int id, String name) {
jdbcTemplate.execute("insert into `student` values (" + id + ",'" + name + "')");
try {
//注意:此时有try catch
orderService.addOrder(3, "110");
System.out.println("addOrder finished");
} catch (Exception e) {
System.out.println(e);
}
}
当代码执行到断点处时,我们看下数据库:
外层事务未完成提交,未新增了一条数据
内层事务完成提交,新增了一条数据,说明外层和内存是互不影响的
2.2 验证PROPAGATION_NESTED 内层事务执行完未立即提交
我们修改内层事务为PROPAGATION_NESTED 类型,并且也在同样的位置打断点,查看数据库数据是否发生变化。
@Transactional(propagation = Propagation.NESTED)
@Override
public void addOrder(int id, String price) {
//order是mysql关键词,必须用`号(tab键上方的那个键,波浪线键)包裹起来
jdbcTemplate.execute("insert into `order` values ("+id+",'"+price+"')");
}
继续走完父事务的代码,发现,2个数据表都多了一条数据。
从整个例子来看,嵌套子事务是整个事务的一部分,提交时,要随着整体才能提交,但是又可以局部回滚,因此这是PROPAGATION_REQUIRES_NEW等做不到的,PROPAGATION_REQUIRES_NEW会独立提交,不是一个整体的概念。
2.3 验证PROPAGATION_NESTED 内层事务回滚
我们模拟在子事务触发非check异常:
@Transactional(propagation = Propagation.NESTED)
@Override
public void addOrder(int id, String price) {
//order是mysql关键词,必须用`号(tab键上方的那个键,波浪线键)包裹起来
jdbcTemplate.execute("insert into `order` values ("+id+",'"+price+"')");
System.out.println( 1/0);
}
一般在父层有灵活的策略:
try {
//注意:此时有try catch
orderService.addOrder(3, "110");
System.out.println("addOrder finished");
} catch (Exception e) {
// 这个地方,可以做很多事情,比如 ServiceC.methodC();
}
在捕获子事务后, 避免异常往上层传递后,这个地方,可以做很多事情,比如 执行ServiceC.methodC(); ,开启另一个子事务,等等。
如果就按照当前的实现的话,子事务会回滚,而父事务顺利提交。
执行结果: