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-声明式事务管理-annotation

spring基于注解的声明式事务控制

spring 声明式事务的坑 @Transactional 注解

Spring Boot 揭秘与实战 数据存储篇 - 声明式事务管理

阶段3 2.Spring_10.Spring中事务控制_7 spring基于注解的声明式事务控制

spring--声明式事务(包含基于注解和基于xml文件的配置方式)