当事务方法被另一个事务方法调用时,必须指定事务应该如何传播,

例如:方法可能继续在现有事务中运行,也可能开启了一个新事务,并在自己的事务中运行。

事务的传播行为可以由传播属性指定,spring定义了种事务传播行为。最常用事务属性有两个:

  1. required:如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务中运行。
  2. required_new:当前的方法必须启动新事务,并在他自己的事务内运行,如果有事务正在运行,应该将它挂起。新事务执行完毕之后,才继续运行旧事务。

在上次的例子中,我们每次只购买一本书,这次我们来试试一次购买N本书,看看旧事务能不能继续起作用。

一、写一个接口类并且实现

实现这个接口类,分别调用上次的purchase方法:

注意:checkout方法需要进行事务管理,所以被@Transactional修饰。

二、写一个测试类

那么问题来了:在@Transactional(propagation=Propagation.REQUIRED)的默认情况(啥属性都不填就默认是REQUIRED)下,如果金额仅仅能够购买一本书,那么是购买一本书,还是两本书都不买呢?

答案是:当金钱只能买一本而买不起两本书的时候,选择一本都不买。

三、试着猜想一下checkout()的执行过程

买第一本书的时候是没有报错的,所以说第一次购买成功了。买第二本书的时候不够钱,则第二本书没有买成,报出错误,进行事务处理。

然而事务处理直接将购买第一本书的事实也进行还原了,是怎么一回事呢?

这是我的猜想:

  1. 当bookService的purchase()方法被另一个事务方法checkout()调用时,checkout()会默认在现有的事务内运行,这个默认的传播行为就是required。因此在checkout()方法的开始和终止边界内只有一个事务,这个事务只在checkout()方法结束时被提交,结果用户一本书都买不了。
  2. 也就是说,checkout()调用了purchase(),也默认使用了事务,所以这个事务归checkout()管理了,而不是每次purchase()都启用了事务管理。这个事务管理只在checkout()的生命周期中启动了一次,而不是在purchase()启动了两次。

从checkout()的层面上看,checkout()的生命周期开始时,启动了事务,当第二次调用purchase()时报错,数据就回滚到checkout()未执行的时间点上了,checkout()生命周期结束,事务结束。

四、如果我们想每个步骤都单独使用新事务呢?

改写bookshopserviceimpl类,将注解改为:

运行结果变成了:可以成功购买第一本书,但是第二本书买不了。

为什么呢?因为REQUIRES_NEW属性允许事务中还有事务。事务的执行流程变成了:

  1. checkout()生命周期开始,事务1启动。
  2. purchase1()开始,事务1被挂起,事务2启动。
  3. purchase1()结束,事务2结束,事务1继续。
  4. purchase2()开始,事务1被挂起,事务3启动。
  5. purchase2()结束,事务3结束,事务1继续。
  6. checkout()生命周期结束,事务1结束。

各种事务就变得独立起来。

第一次买成功了,purchase1()结束,事务2结束。

第二次买失败了,报错。事务3回滚的是purchase2(),不会干涉到别的事务。

五、总结

非常重要的概念,需要理解并且熟练使用。

发表评论

电子邮件地址不会被公开。 必填项已用*标注