Тестовый модуль Spring / JTA / JPA: откат не работает - PullRequest
5 голосов
/ 05 октября 2009

Я пытаюсь проверить сущность EJB3 с помощью Spring.

Сам EJB не использует Spring, и я хотел бы, чтобы дублирование рабочей конфигурации JPA было минимальным (т. Е. Не дублировать persistence.xml для примера).

Кажется, что мои модульные тесты работают, но, хотя мои модульные тесты должны быть транзакционными, данные сохраняются между различными методами тестирования ...

Вот моя сущность:

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>

Когда я запускаю свой тест, test2 завершается сбоем, потому что он находит 2 объекта, где я ожидал только один (потому что первый должен был откатиться ...)

Я перепробовал много разных конфигураций, и эта, кажется, самая полная, которую я могу получить ... У меня нет других идей. Вы?

Ответы [ 4 ]

2 голосов
/ 07 октября 2009

Когда я пытался интегрировать JOTM и Hibernate, мне в итоге пришлось кодировать мою реализацию ConnectionProvider. Вот как это выглядит прямо сейчас: http://pastebin.com/f78c66e9c

Затем вы указываете свою реализацию в качестве владельца соединения в свойствах гибернации, и транзакции магически начинают работать.

Дело в том, что провайдер соединений по умолчанию вызывает метод getConnection () для источника данных. В вашей собственной реализации вы вызываете getXAConnection (). GetConnection (). Это имеет значение

2 голосов
/ 07 октября 2009

Мне удалось заставить его работать, используя Bitronix вместо JOTM. Bitronix предоставляет LrcXADataSource, который позволяет базе данных не XA участвовать в транзакции JTA.

Я думаю, что проблемы заключались в том, что H2 не соответствует XA, и Enhydydra StandardXADataSource не делает это волшебным образом (я также прекратил использовать HSQLDB, но это не связано с проблемой).

Вот мой весенний контекст, который работает:

<?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>
1 голос
/ 06 октября 2009

Редактировать: (Извините, кажется, я только наполовину проснулся, когда писал этот абзац. Конечно, вы правы, все должно быть откатано по умолчанию.)

Вы можете проверить, что на самом деле делает менеджер транзакций, например, включив для него вывод отладки.

Предполагая log4j:

log4j.logger.org.springframework.transaction=DEBUG

Менеджер транзакций дает вам очень хороший вывод журнала о созданных и присоединенных транзакциях, а также о коммитах и ​​откате. Это должно помочь вам выяснить, что не работает с вашей настройкой.

0 голосов
/ 27 февраля 2018

Добавьте аннотацию @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.
...