在开发中,相信你们都使用过Spring的事务管理功能。那么,你是否有了解过,Spring的事务传播行为呢?java
Spring中,有7种类型的事务传播行为。事务传播行为是Spring框架提供的一种事务管理方式,它不是数据库提供的。不知道你们是否据说过“不要在service事务方法中嵌套事务方法,这样会提交多个事务”的说法,其实这是不许确的。了解了事务传播行为以后,相信你就会明白!mysql
本文首发于头条号【Happyjava】。Happy的掘金地址:https://juejin.im/user/5cc2895df265da03a630ddca,Happy的我的博客:(http://blog.happyjava.cn)[http://blog.happyjava.cn]。欢迎转载,但须保留此段声明。spring
事务的传播行为,默认值为 Propagation.REQUIRED。能够手动指定其余的事务传播行为,以下:sql
若是当前存在事务,则加入该事务,若是当前不存在事务,则建立一个新的事务。数据库
若是当前存在事务,则加入该事务;若是当前不存在事务,则以非事务的方式继续运行。app
若是当前存在事务,则加入该事务;若是当前不存在事务,则抛出异常。框架
从新建立一个新的事务,若是当前存在事务,延缓当前的事务。ide
以非事务的方式运行,若是当前存在事务,暂停当前的事务。测试
以非事务的方式运行,若是当前存在事务,则抛出异常。spa
若是没有,就新建一个事务;若是有,就在当前事务中嵌套其余事务。
数据库表:
CREATE TABLE `t_user` ( `id` int(11) NOT NULL, `password` varchar(255) DEFAULT NULL, `username` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
一个整合了Spring Data JPA的SpringBoot工程,这里就很少说了。
默认的事务传播行为是Propagation.REQUIRED,也就是说:若是当前存在事务,则加入该事务,若是当前不存在事务,则建立一个新的事务。
下面,咱们就验证下前面说的“不要循环嵌套事务方法”的问题:
如今有两个Service,以下:
@Service public class UserService { @Autowired private UserRepo userRepo; @Transactional(propagation = Propagation.REQUIRED) public void insert() { UserEntity user = new UserEntity(); user.setUsername("happyjava"); user.setPassword("123456"); userRepo.save(user); } }
这里很简单,就一个insert插入用户的方法。
@Service public class UserService2 { @Autowired private UserService userService; @Transactional public void inserBatch() { for (int i = 0; i < 10; i++) { if (i == 9) { throw new RuntimeException(); } userService.insert(); } } }
注入UserService,循环十次调用参数方法。而且第十次抛出异常。调用inserBatch方法,查看结果:
@Test public void insertBatchTest() { userService2.inserBatch(); }
结果以下:
数据库中没有记录:
这也证实了“若是当前存在事务,则加入该事务”的概念。若是之后还碰到有人说不要循环嵌套事务的话,能够叫他回去好好看看Spring的事务传播行为。
若是当前存在事务,则加入该事务;若是当前不存在事务,则以非事务的方式继续运行。也就是说,该模式是否支持事务,看调用它的方法是否有事务支持。测试代码以下:
@Transactional(propagation = Propagation.SUPPORTS) public void insert() { UserEntity user = new UserEntity(); user.setUsername("happyjava"); user.setPassword("123456"); userRepo.save(user); throw new RuntimeException(); }
public void insertWithoutTx() { userService.insert(); }
调用的方法没有开启事务,运行结果:
运行报错了,可是数据却没有回滚掉。说明了insert方法是没有在事务中运行的。
若是当前存在事务,则加入该事务;若是当前不存在事务,则抛出异常。mandatory中文是强制性的意思,代表了被修饰的方法,必定要在事务中去调用,不然会抛出异常。
@Transactional(propagation = Propagation.MANDATORY) public void insert() { UserEntity user = new UserEntity(); user.setUsername("happyjava"); user.setPassword("123456"); userRepo.save(user); }
UserService2.java
public void insertWithoutTx() { userService.insert(); }
调用:
@Test public void insertWithoutTxTest() { userService2.insertWithoutTx(); }
运行结果:
抛出了异常,提示没有存在的事务。
这个理解起来可能会比较绕,官方的解释是这样子的:
Create a new transaction, and suspend the current transaction if one exists.
大意就是:从新建立一个新的事务,若是当前存在事务,延缓当前的事务。这个延缓,或者说挂起,可能理解起来比较难,下面经过例子来分析:
@Transactional(propagation = Propagation.REQUIRES_NEW) public void insert() { UserEntity user = new UserEntity(); user.setUsername("happyjava"); user.setPassword("123456"); userRepo.save(user); }
这个insert方法的传播行为改成REQUIRES_NEW。
@Transactional public void inserBatch() { UserEntity user = new UserEntity(); user.setUsername("初次调用"); user.setPassword("123456"); userRepo.save(user); for (int i = 0; i < 10; i++) { if (i == 9) { throw new RuntimeException(); } userService.insert(); } }
inserBatch拥有事务,而后后面循环调用的insert方法也有本身的事务。根据定义,inserBatch的事务会被延缓。具体表现就是:后面的10次循环的事务在每次循环结束以后都会提交本身的事务,而inserBatch的事务,要等循环方法走完以后再提交。但因为第10次循环会抛出异常,则inserBatch的事务会回滚,既数据库中不会存在:“初次调用”的记录:
测试代码:
@Test public void insertBatchTest() { userService2.inserBatch(); }
执行结果:
这种状况,符合开始说的“不要循环嵌套事务方法”的说话,固然是否须要循环嵌套,仍是要看业务逻辑的。
Execute non-transactionally, suspend the current transaction if one exists.
以非事务的方式运行,若是当前存在事务,暂停当前的事务。这种方式与REQUIRES_NEW有所相似,可是NOT_SUPPORTED修饰的方法其自己是没有事务的。这里就不作代码演示了。
以非事务的方式运行,若是当前存在事务,则抛出异常。
@Transactional(propagation = Propagation.NEVER) public void insert() { UserEntity user = new UserEntity(); user.setUsername("happyjava"); user.setPassword("123456"); userRepo.save(user); }
@Transactional public void insertWithTx() { userService.insert(); }
执行结果:
若是没有事务,就新建一个事务;若是有,就在当前事务中嵌套其余事务。
这个也是理解起来比较费劲的一个行为。咱们一步一步分析。
外围方法没有事务:这种状况跟REQUIRED是同样的,会新建一个事务。
外围方法若是存在事务:这种状况就会嵌套事务。所谓嵌套事务,大意就是,外围事务回滚,内嵌事务必定回滚,而内嵌事务能够单独回滚而不影响外围主事务和其余子事务。
因为本人使用Spring Data JPA 进行的演示代码,使用嵌套事务会提示:
org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities
搜索了下,hibernate彷佛不支持这种事务传播方式。因此这里就不作演示了
事务传播行为,在开发中可能不会特别的留意到它(更多时候,咱们可能只是使用默认的方式),可是仍是须要对其要有所理解。但愿本篇文章能让你们明白Spring的7种事务传播行为。