2009-10-05 52 views
5

我想用Spring测试实体EJB3。Spring/JTA/JPA单元测试:回滚不起作用

EJB本身不使用Spring,我想保持生产JPA配置的重复最小化(即不重复例如persistence.xml)。

我的单元测试,似乎工作,但即使我的单元测试应该是transactionnal,数据持久化的各种测试方法之间......

这里是我的实体:

package sample; 

import javax.persistence.Entity; 
import javax.persistence.GeneratedValue; 
import javax.persistence.Id; 

@Entity 
public class Ejb3Entity { 

    public Ejb3Entity(String data) { 
     super(); 
     this.data = data; 
    } 
    private Long id; 
    private String data; 

    @Id 
    @GeneratedValue 
    public Long getId() { 
     return id; 
    } 
    public void setId(Long id) { 
     this.id = id; 
    } 

    public String getData() { 
     return data; 
    } 
    public void setData(String data) { 
     this.data = data; 
    } 

} 

我的单元测试:

package sample; 

import static org.junit.Assert.*; 

import javax.persistence.EntityManager; 
import javax.persistence.PersistenceContext; 

import org.junit.Before; 
import org.junit.Test; 
import org.junit.runner.RunWith; 
import org.springframework.test.context.ContextConfiguration; 
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 
import org.springframework.transaction.annotation.Transactional; 

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(locations={"/appContext.xml"}) 
@Transactional 
public class Ejb3EntityTest { 

    @PersistenceContext 
    EntityManager em; 

    @Before 
    public void setUp() throws Exception { 
     Ejb3Entity one = new Ejb3Entity("Test data"); 
     em.persist(one); 
    } 

    @Test 
    public void test1() throws Exception { 

     Long count = (Long) em.createQuery("select count(*) from Ejb3Entity").getSingleResult(); 
     assertEquals(Long.valueOf(1l), count); 
    } 

    @Test 
    public void test2() throws Exception { 

     Long count = (Long) em.createQuery("select count(*) from Ejb3Entity").getSingleResult(); 
     assertEquals(Long.valueOf(1l), count); 
    } 

} 

和我appContext.xml:

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" 
    xmlns:context="http://www.springframework.org/schema/context" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
      http://www.springframework.org/schema/tx 
      http://www.springframework.org/schema/tx/spring-tx.xsd 
      http://www.springframework.org/schema/context 
      http://www.springframework.org/schema/context/spring-context.xsd"> 

    <bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean" /> 

    <bean id="transactionManager" 
     class="org.springframework.transaction.jta.JtaTransactionManager"> 
     <property name="userTransaction" ref="jotm" /> 
     <property name="allowCustomIsolationLevels" value="true" /> 
    </bean> 

    <bean id="dataSource" class="org.enhydra.jdbc.standard.StandardXADataSource"> 
     <property name="driverName" value="org.h2.Driver" /> 
     <property name="url" value="jdbc:h2:mem:unittest;DB_CLOSE_DELAY=-1" /> 
     <property name="user" value="" /> 
     <property name="password" value="" /> 
     <property name="transactionManager" ref="jotm" /> 
    </bean> 

    <bean id="emf" 
     class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
     <property name="persistenceUnitPostProcessors"> 
      <bean class="sample.JtaDataSourcePersistenceUnitPostProcessor"> 
       <property name="jtaDataSource" ref="dataSource" /> 
      </bean> 
     </property> 
     <property name="jpaVendorAdapter"> 
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
       <property name="showSql" value="false" /> 
       <property name="generateDdl" value="true" /> 
       <property name="database" value="H2" /> 
       <property name="databasePlatform" value="org.hibernate.dialect.H2Dialect" /> 
      </bean> 
     </property> 
     <property name="jpaPropertyMap"> 
      <map> 
       <entry key="hibernate.transaction.manager_lookup_class" 
        value="org.hibernate.transaction.JOTMTransactionManagerLookup" /> 
       <entry key="hibernate.transaction.auto_close_session" value="false" /> 
       <entry key="hibernate.current_session_context_class" value="jta" /> 
      </map> 
     </property> 

    </bean> 


</beans> 

当我运行我的测试,测试2,因为它发现

我已经尝试了很多不同的配置2的实体,我希望只有一个(因为第一个应该被rollbacked ...)而这一次似乎失败是我能得到的最全面的...我没有其他想法。你做 ?

+0

为什么你认为第一个实体应该被回滚? – janko 2009-10-05 19:35:46

+0

因为我使用了@Transactional注解,它使得每个测试运行都使用自己的事务,而这个事务是由Spring自动回滚的。 – Michel 2009-10-06 08:56:45

回答

2

我设法使用Bitronix而不是JOTM工作。 Bitronix提供了允许非XA数据库参与JTA事务的LrcXADataSource。

我认为问题是H2不符合XA标准,并且enhydra StandardXADataSource也没有如此神奇(我也使用HSQLDB结束,但与问题无关)。

这里是我的工作Spring上下文:

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" 
    xmlns:context="http://www.springframework.org/schema/context" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
      http://www.springframework.org/schema/tx 
      http://www.springframework.org/schema/tx/spring-tx.xsd 
      http://www.springframework.org/schema/context 
      http://www.springframework.org/schema/context/spring-context.xsd"> 

    <context:annotation-config /> 
    <tx:annotation-driven transaction-manager="transactionManager" /> 

    <!-- Bitronix Transaction Manager embedded configuration --> 
    <bean id="btmConfig" factory-method="getConfiguration" 
     class="bitronix.tm.TransactionManagerServices"> 
     <property name="serverId" value="spring-btm" /> 
     <property name="journal" value="null" /> 
    </bean> 

    <!-- create BTM transaction manager --> 
    <bean id="BitronixTransactionManager" factory-method="getTransactionManager" 
     class="bitronix.tm.TransactionManagerServices" depends-on="btmConfig,dataSource" 
     destroy-method="shutdown" /> 

    <bean id="transactionManager" 
     class="org.springframework.transaction.jta.JtaTransactionManager"> 
     <property name="transactionManager" ref="BitronixTransactionManager" /> 
     <property name="userTransaction" ref="BitronixTransactionManager" /> 
     <property name="allowCustomIsolationLevels" value="true" /> 
    </bean> 


    <!-- DataSource definition --> 

    <bean id="dataSource" class="bitronix.tm.resource.jdbc.PoolingDataSource" 
     init-method="init" destroy-method="close"> 
     <property name="className" value="bitronix.tm.resource.jdbc.lrc.LrcXADataSource" /> 
     <property name="uniqueName" value="unittestdb" /> 
     <property name="minPoolSize" value="1" /> 
     <property name="maxPoolSize" value="3" /> 
     <property name="allowLocalTransactions" value="true" /> 
     <property name="driverProperties"> 
      <props> 
       <prop key="driverClassName">org.hsqldb.jdbcDriver</prop> 
       <prop key="url">jdbc:hsqldb:mem:unittestdb</prop> 
       <prop key="user">sa</prop> 
       <prop key="password"></prop> 
      </props> 
     </property> 
    </bean> 

    <!-- Entity Manager Factory --> 
    <bean id="emf" 
     class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
     <property name="dataSource" ref="dataSource" /> 
     <property name="jpaVendorAdapter"> 
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
       <property name="showSql" value="true" /> 
       <property name="generateDdl" value="true" /> 
       <property name="database" value="HSQL" /> 
      </bean> 
     </property> 
     <property name="jpaPropertyMap"> 
      <map> 
       <entry key="hibernate.transaction.manager_lookup_class" 
        value="org.hibernate.transaction.BTMTransactionManagerLookup" /> 
       <entry key="hibernate.transaction.auto_close_session" value="false" /> 
       <entry key="hibernate.current_session_context_class" value="jta" /> 
      </map> 
     </property> 

    </bean> 
+0

在这里张贴一周后,我遇到了同样的问题。我也无法让JOTM正确回滚;它总是说它正在回滚,但交易的变化仍然在数据库中。 BTM完成这项工作很好,无论是MySQL还是H2后端。奇怪的。 – Henning 2009-10-13 18:04:40

1

编辑:(对不起,看来我只有半梦半醒的时候,我写这一段你当然是对的,一切都应该被默认回滚)

你可以检查哪些事务管理器是真的在做,例如通过启用它的调试输出。

假设的log4j:

log4j.logger.org.springframework.transaction=DEBUG 

事务管理器提供了有关创建和加入交易非常漂亮的日志输出,也关于提交和回滚。这应该可以帮助您找出哪些不适用于您的设置。

+0

感谢您的建议。有更多的日志帮助了很多。 – Michel 2009-10-07 15:59:27

2

当我试图集成JOTM和Hibernate时,最终我不得不编写ConnectionProvider的实现代码。这里是它现在的样子:http://pastebin.com/f78c66e9c

然后你指定你的实现作为hibernate属性和事务中的连接privider神奇地开始工作。

问题是默认连接提供程序在数据源上调用getConnection()。在你自己的实现中,你可以调用getXAConnection()。getConnection()。这使差异

+0

对不起,我结束了使用BTM而不是JOTM,我没有机会做你的建议。 – Michel 2009-10-07 16:00:19

0

添加注释@Rollback(从org.springframework.test.annotation),就像春天文档中提到的@Transactional注释之后。

@Rollback is a test annotation that is used to indicate whether a test- 
managed transaction should be rolled back after the test method has 
completed. 
Consult the class-level Javadoc for 
org.springframework.test.context.transaction.TransactionalTest- 
ExecutionListener for an explanation of test-managed transactions. 

When declared as a class-level annotation, @Rollback defines the default 
rollback semantics for all test methods within the test class hierarchy. When 
declared as a method-level annotation, @Rollback defines rollback semantics 
for the specific test method, potentially overriding class-level default 
commit or rollback semantics. 

As of Spring Framework 4.2, @Commit can be used as direct replacement for 
@Rollback(false). 

Warning: Declaring @Commit and @Rollback on the same test method or on the 
same test class is unsupported and may lead to unpredictable results. 

This annotation may be used as a meta-annotation to create custom composed 
annotations. Consult the source code for @Commit for a concrete example. 
相关问题