spring 声明式事务管理在真实的Service和单元测试时的回滚情况,需要注意的问题,jpa为例子
Posted shenbushen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring 声明式事务管理在真实的Service和单元测试时的回滚情况,需要注意的问题,jpa为例子相关的知识,希望对你有一定的参考价值。
如何测试事务,测试事务回滚情况:
我做了大量的不同的测试:
场景1:
Service层中使用注解@Transactional,注解@PersistenceContext private EntityManager emt;
写了两个方法
public void insertfail() //插入失败要回滚 { for(int i=0;i<20;i++) { User users=new User(); users.setEmail("[email protected]"+i); if(i==10) { int a=1/0; } this.emt.persist(users); } } public void insertsuccess() //正常插入 { for(int i=0;i<20;i++) { User users=new User(); users.setEmail("[email protected]"+i); this.emt.persist(users); } }
使用spring-test,junit4进行单元测试
单元测试中不带 @Transactional 和@TransactionConfiguration(defaultRollback=false),然后使用注解激活Service ,写两个测试方法,就是直接调用service的方法
@Autowired private uservice usv; @Test public void testshiwu_fail(){ this.usv.insertfail(); } @Test public void testshiwu_success(){ this.usv.insertsuccess(); }
最后测试结果:
成功的: Hibernate: insert into User (changetime, email, password, registtime, username) values (?, ?, ?, ?, ?) 11:48:43.960 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction commit 11:48:43.960 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [[email protected]] 11:48:44.038 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [[email protected]] after transaction 11:48:44.038 [main] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager 要回滚的:因为出现除以0的错误所以回滚: Hibernate: insert into User (changetime, email, password, registtime, username) values (?, ?, ?, ?, ?) 11:50:55.662 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction rollback 11:50:55.662 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Rolling back JPA transaction on EntityManager [[email protected]] 11:50:56.416 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.Enti[email protected]] after transaction 11:50:56.416 [main] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
这就完结了吗?当然没有:场景一是一个常见的服务层的事务测试。如果我们正确插入,则真的插进数据库了;如果我们运行出现错误,那就回滚了。这是显而易见的。现在我要说的是
场景二:正确插入数据时,也回滚数据,但是我们获取到已经插入的信息,这可以保护数据现场。
@TransactionConfiguration(defaultRollback=true)
第一种:不保护数据现场
Service层代码不变,test中的测试代码也不便,但是在test中加入 @Transactional @TransactionConfiguration(defaultRollback=false) //开启事务,并不回滚
测试结果是:
正确插入的方法: Hibernate: insert into User (changetime, email, password, registtime, username) values (?, ?, ?, ?, ?) 11:59:52.169 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction commit 11:59:52.169 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [[email protected]] 11:59:52.356 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [[email protected]] after transaction 11:59:52.356 [main] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager 11:59:52.372 [main] INFO o.s.t.c.t.TransactionContext - Committed transaction for test context 回滚的: Hibernate: insert into User (changetime, email, password, registtime, username) values (?, ?, ?, ?, ?) 12:04:26.861 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Participating transaction failed - marking existing transaction as rollback-only 12:04:26.861 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Setting JPA transaction on EntityManager [[email protected]] rollback-only 12:04:26.861 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction commit 12:04:26.861 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [[email protected]] 12:04:26.939 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [[email protected]] after transaction 12:04:26.939 [main] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager 12:04:26.970 [main] WARN o.s.test.context.TestContextManager - Caught exception while allowing TestExecutionListener Could not commit JPA transaction; Transaction marked as rollbackOnly虽然最终的结果和场景一是一样的,但是日志却有点不同(正确插入时:service的事务正确提交插入,到了测试层,因为声明了事务,而且设置为不回滚,所以测试层就提交了事务,所以正确插入了数据库;错误插入时:service就已经回滚了请求,所以test层提不提交都没关系了)
,
第二种:保护数据现场:将test层改为@Transactional @TransactionConfiguration(defaultRollback=true)
正确插入: Hibernate: insert into User (changetime, email, password, registtime, username) values (?, ?, ?, ?, ?) 12:15:22.067 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction rollback 12:15:22.067 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Rolling back JPA transaction on EntityManager [[email protected]] 12:15:22.157 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [[email protected]] after transaction 12:15:22.157 [main] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager 12:15:22.157 [main] INFO o.s.t.c.t.TransactionContext - Rolled back transaction for test context 错误插入: Hibernate: insert into User (changetime, email, password, registtime, username) values (?, ?, ?, ?, ?) 12:16:18.037 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Participating transaction failed - marking existing transaction as rollback-only 12:16:18.037 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Setting JPA transaction on EntityManager [[email protected]] rollback-only 12:16:18.053 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction rollback 12:16:18.053 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Rolling back JPA transaction on EntityManager [[email protected]] 12:16:18.205 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [[email protected]] after transaction 12:16:18.205 [main] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager 12:16:18.205 [main] INFO o.s.t.c.t.TransactionContext - Rolled back transaction for test context
分析:是不是很神奇,正确插入了,但是居然回滚了,因此也就保护了数据现场。而错误插入,早就在service中因为事务失败回滚了,所以test的回滚不回滚也没什么用。当然就算成功,也会被回滚。
注意的地方:@Transactional 与 @TransactionConfiguration(defaultRollback=true)之间的关系是,只有设置了@Transactional,@TransactionConfiguration(defaultRollback=true)才有可能起作用,否则没卵用。当然如果设置了@Transactional,不设置@TransactionConfiguration(defaultRollback=true)的话,默认是回滚的。
场景三:绕过Service层来测事务(会很坑爹)
首先:引入事务,并设置为不回滚,且引入事务管理对象@PersistenceContext private EntityManager emt;
@Transactional @TransactionConfiguration(defaultRollback=false) @PersistenceContext private EntityManager emt;
两个测试绕过Service的操作方法
@Test public void testwusuccess(){ //正常插入 for(int i=0;i<20;i++) { User user=new User(); user.setEmail("[email protected]"+i); emt.persist(user); } } @Test public void testwufail() //非正常插入 { for(int i=0;i<20;i++) { User user=new User(); user.setEmail("[email protected]"+i); if(i==10) { int a=5/0; } emt.persist(user); } }
观察结果:
正常插入情况的结果 Hibernate: insert into User (changetime, email, password, registtime, username) values (?, ?, ?, ?, ?) 12:38:44.699 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction commit 12:38:44.699 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [[email protected]] 12:38:44.855 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [[email protected]] after transaction 12:38:44.855 [main] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager 12:38:44.855 [main] INFO o.s.t.c.t.TransactionContext - Committed transaction for test context 非正常插入情况的结果: 12:41:24.759 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction commit 12:41:24.759 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [[email protected]] 12:41:24.822 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [[email protected]] after transaction 12:41:24.822 [main] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager 12:41:24.822 [main] INFO o.s.t.c.t.TransactionContext - Committed transaction for test context
我擦,居然一样,难道是我没保存,直接就测试了,绝对不是,正常插入没抛出异常,错误插入抛出异常。且正常插入插了20条,非正常插入只插了前10条。说好的回滚呢。
我在这里测了一遍又一遍:包括在方法里加@Transation,并添加事务的各种模式,包括主动抛出各种异常,包括为@Transation设置各种默认异常检测(我们知道声明式事务是默认检测RuntionExction的,但是我这里的除0正式RuntionExction中的啊),所以我也直接改掉默认检测,最终结果还是没什么卵用,依旧不回滚。在各种各样的测试中,我发现有一种是可以回滚的。如下所示:
@Test public void testwufail() { for(int i=0;i<20;i++) { User user=new User(); user.setEmail("[email protected]"+i); if(i==10) { user.setId(26); } emt.persist(user); } }
Hibernate: insert into User (changetime, email, password, registtime, username) values (?, ?, ?, ?, ?) 12:53:44.306 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction commit 12:53:44.306 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [[email protected]] 12:53:44.370 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [[email protected]] after transaction 12:53:44.370 [main] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager 12:53:44.378 [main] WARN o.s.test.context.TestContextManager - Caught exception while allowing TestExecutionListener [org.springframew[email protected]1e412161] to process 'after' execution for test: method [public void com.wenyan.test.jpatest.testwufail()], instance [[email protected]], exception [javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.wenyan.model.User] org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; Transaction marked as rollbackOnly
但是我要的是可以回滚所有的RuntimeExction啊,所以这没卵用
还有一种可以回滚:那就是设置个
@Transactional @TransactionConfiguration(defaultRollback=true)
但是:这是事务本身不回滚,但是test帮我们回滚了,这两个是不同范畴。依旧没卵用
过渡性总结:
1.做单元测试时,还是按规范来测,测事务千万不要绕过Service中的@Transactional,否则会很悲催。原因是:Service中的@Transactional有效管理事务的周期,Test中的@Transational中是管理数据现场的,经过数据操作事务本身后还要经过test这一层来决定回不回滚。所以可以说这两个层面的@Transactional干的是不同范畴的事情。 虽然如此,我相信应该是可以直接绕过Service去做事务的单元测试的,但是我还没发现,因此还是按规范来测好
2.如果说Test层次的@Transational没有一点事务本身管理的功能,这显然也是有点矛盾的。当我手动事务管理时,我要开启事务,然后进行操作,最后提交,然后关闭:
<pre class="html" name="code"> @Test //这是一个分正常插入的例子 public void testshiwu(){ EntityManager em=this.getEmf().createEntityManager(); em.getTransaction().begin(); for(int i=0;i<20;i++) { User user=new User(); user.setEmail("[email protected]"+i); if(i==10) { int a=5/0; } em.persist(user); } em.getTransaction().commit(); em.close(); }
在遇到异常时,不会提交,所以也就没有什么回滚这类的事。如果正常则会提交。
如果是声明式管理,我们不用开启事务,和提交事务,就可以直接插入数据库,但是奇怪的是测试中的居然不帮我们在异常的时候回滚。我觉得最大可能是冲突吧。所以我个人觉得测试中的@Transation起到了开启事务和提交事务的功能。但没起到错误时回滚的功能。
场景四:
看看真实的情况下和测试的情况下有什么不同吧:
1.当不在Service中声明@Transation时:使用规范的接口函数persist()等函数,出现没有事务的异常,且并不能插入数据库,使用实现了的接口函数譬如save()这样的,则可以插进函数,因为其默认存在事务管理,但是不具备回宫特性。
2.当我在Service中声明了@Transation时:使用规范接口函数persist()等函数,仍然出现没有事务的异常,且不能插进数据库。
分析:这是什么原因呢:原来我使用的是Spring mvc框架,在mvc配置文件时扫描包扫描了整个包,(包括Service中的包),自然会把@Trancetion当成普通的bean,故没有事务管理。
譬如mvc配置文件中使用了
<context:component-scan base-package="com.wenyan"></context:component-scan> 这相当于全包扫描。wenyan包中既包含了controller,又包含了Service,故导致Service中声明的事务被当成了简单的bean进行使用,所以自然没有事务的管理特性。
解决方法:
让包扫描的设置不包含Service:有两种方式:
第一:直接扫描controller包:<context:component-scan base-package="com.wenyan.controller"></context:component-scan>
第二:全包扫描的时候:把Service忽略:
<context:component-scan base-package="com.wenyan">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
</context:component-scan>
这两种方法都可以解决此问题。
3.因此当我们即加入了事务声明,又不扫描Service时,使用规范接口的函数persist(),和实现了的接口函数save()都具有事务管理的特性。即不用注意,开启,提交,关闭,回滚等操作,它帮我们自动搞定了。
最终总结:
[email protected]在Service和Test 中的声明是不同的范畴,Service中声明了,那么事务管理特性就存在了。Test中声明,则拥有了开启,提交,关闭的特性,但是不拥有回滚特性,且在Test中的Transation最重要的是决定最终是否提交到数据库,这里起到了一个保护数据现场的作用。
2.在集成Spring mvc时,在mvc配置文件中不能扫描带了@Service的类,这会导致事务变成普通的bean,而不是带有事务管理特性的bean。
3.实现了的接口函数save()等,无需@Transation也能实现开启,提交,关闭等特性,但是多条操作的时候,没有回滚特性。
4.无论是规范的接口persist还是实现了的接口save,都需要@Transation的支持,才都具有事务的完全特性。
5..为何实现了的接口具有开启事务,自动提交,关闭事务的功能,但是没有回滚特性呢;
原因在于:配置Spring mvc时我们就已经让他,有了工厂方法,和事务管理,这里底层应该做的就是我们手工生成事务,开启,关闭,提交的功能。
<jpa:repositories base-package="com.wenyan.dao" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager">
</jpa:repositories>
6.还是按规范来测试和写代码,那么就不用考虑那么多,若想不规范,则必须弄懂之间的原理美方能做到游刃有余。
以上是关于spring 声明式事务管理在真实的Service和单元测试时的回滚情况,需要注意的问题,jpa为例子的主要内容,如果未能解决你的问题,请参考以下文章
spring 声明式事务的坑 @Transactional 注解
Spring Boot 揭秘与实战 数据存储篇 - 声明式事务管理