java Spring/JTA/JPA 单元测试:回滚不起作用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1519968/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 16:53:58  来源:igfitidea点击:

Spring / JTA / JPA unit test : Rollback not working

javahibernatespringjunitjta

提问by Michel

I am trying to test an entity EJB3 with Spring.

我正在尝试使用 Spring 测试实体 EJB3。

The EJB itself does not uses Spring and I would like to keep duplications of the production JPA configuration minimal (ie not duplicating persistence.xml for exemple).

EJB 本身不使用 Spring,我希望尽量减少生产 JPA 配置的重复(例如,不重复persistence.xml)。

My unit tests seems to work but even though my unit tests should be transactionnal, data is persisted between the various test methods ...

我的单元测试似乎有效,但即使我的单元测试应该是事务性的,数据仍会在各种测试方法之间保留......

Here is my entity :

这是我的实体:

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;
    }

}

My unit test :

我的单元测试:

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);
    }

}

and my appContext.xml :

和我的 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>

When I run my test, test2 fails because it finds 2 entity where I expected only one (because the first one should have been rollbacked ...)

当我运行我的测试时,test2 失败了,因为它找到了 2 个我期望只有一个的实体(因为第一个应该已经回滚了......)

I have tried a lot of different configurations and this one seems to be the most comprehensive I can get ... I have no other ideas. Do you ?

我尝试了很多不同的配置,这个似乎是我能得到的最全面的......我没有其他想法。你 ?

采纳答案by Michel

I managed to make it work using Bitronixinstead of JOTM. Bitronix provides a LrcXADataSource that allows a non XA database to participate in the JTA transaction.

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

I think the issues were that H2 is not XA compliant and the enhydra StandardXADataSourcedoes not make it magically so (I also ended using HSQLDB but that is unrelated to the issue).

我认为问题在于 H2 不符合 XA 并且 enhydraStandardXADataSource并没有神奇地使它如此(我也结束了使用 HSQLDB 但这与问题无关)。

Here is my spring context that works :

这是我的 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>

回答by artemb

When I was trying to integrate JOTM and Hibernate, I eventually ended up having to code my implementation of ConnectionProvider. Here is what it looks like right now: http://pastebin.com/f78c66e9c

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

Then you specify your implementation as the connection privider in hibernate properties and transactions magically start to work.

然后你将你的实现指定为休眠属性中的连接特权,事务神奇地开始工作。

The thing is that the default connection provider calls getConnection() on the datasource. In you own implementation you call getXAConnection().getConnection(). This makes the difference

问题是默认连接提供程序在数据源上调用 getConnection()。在您自己的实现中,您调用 getXAConnection().getConnection()。这有区别

回答by Henning

Edit:(Sorry, seems I was only half awake when I wrote this paragraph. Of course you're right, everything should be rolled back by default.)

编辑:(对不起,当我写这一段时,我似乎只是半睡半醒。当然你是对的,默认情况下一切都应该回滚。)

You could check what the transaction manager is really doing, for example by enabling debug output for it.

您可以检查事务管理器真正在做什么,例如通过为其启用调试输出。

Assuming log4j:

假设log4j:

log4j.logger.org.springframework.transaction=DEBUG

The transaction manager gives you very nice log output about created and joined transactions, and also about commits and rollbacks. That should help you find out what isn't working with your setup.

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

回答by Nageswara RaO Maridu

Add @Rollback annotation (from org.springframework.test.annotation), just after the @Transactional annotation as mentioned in the spring documentation.

添加 @Rollback 注释(来自org.springframework.test.annotation),就在 spring 文档中提到的 @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.