您的位置:首页 > 编程语言 > Java开发

spring 声明式事务管理在真实的Service和单元测试时的回滚情况,需要注意的问题,jpa为例子

2016-06-03 14:51 811 查看
如何测试事务,测试事务回滚情况:我做了大量的不同的测试:场景1:Service层中使用注解@Transactional,注解@PersistenceContext private EntityManager emt;写了两个方法
public	void insertfail()                //插入失败要回滚
{

for(int i=0;i<20;i++)
{
User users=new User();
users.setEmail("sds@qq.com"+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("sds@qq.com"+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 [org.hibernate.jpa.internal.EntityManagerImpl@6d214a1e]
11:48:44.038 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@6d214a1e] 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 [org.hibernate.jpa.internal.EntityManagerImpl@62bb0f89]
11:50:56.416 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@62bb0f89] 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 [org.hibernate.jpa.internal.EntityManagerImpl@ea8c62a]
11:59:52.356 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@ea8c62a] 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 [org.hibernate.jpa.internal.EntityManagerImpl@57d50bad] 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 [org.hibernate.jpa.internal.EntityManagerImpl@57d50bad]
12:04:26.939 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@57d50bad] 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 [org.hibernate.jpa.internal.EntityManagerImpl@63f1b7e7]
12:15:22.157 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@63f1b7e7] 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 [org.hibernate.jpa.internal.EntityManagerImpl@52d1b33a] 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 [org.hibernate.jpa.internal.EntityManagerImpl@52d1b33a]
12:16:18.205 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@52d1b33a] 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("sdsd@qqs.com"+i);

emt.persist(user);
}
}

@Test
public void testwufail()                   //非正常插入
{
for(int i=0;i<20;i++)
{
User user=new User();
user.setEmail("sdsd@qqs.com"+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 [org.hibernate.jpa.internal.EntityManagerImpl@119cb48]
12:38:44.855 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@119cb48] 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 [org.hibernate.jpa.internal.EntityManagerImpl@6f16a8ba]
12:41:24.822 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@6f16a8ba] 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("sdsd@qqs.com"+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 [org.hibernate.jpa.internal.EntityManagerImpl@793b23eb]
12:53:44.370 [main] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@793b23eb] 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.springframework.test.context.transaction.TransactionalTestExecutionListener@1e412161] to process 'after' execution for test: method [public void com.wenyan.test.jpatest.testwufail()], instance [com.wenyan.test.jpatest@611c3b04], 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("sdsd@qqs.com"+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()都具有事务管理的特性。即不用注意,开启,提交,关闭,回滚等操作,它帮我们自动搞定了。最终总结:1.@Transation在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.还是按规范来测试和写代码,那么就不用考虑那么多,若想不规范,则必须弄懂之间的原理美方能做到游刃有余。
                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: