2009-10-05 8 views
5

Ich versuche eine Entity EJB3 mit Spring zu testen.Spring/JTA/JPA Komponententest: Rollback funktioniert nicht

Die EJB selbst verwendet Spring nicht und ich möchte doppelte Kopien der Produktion JPA-Konfiguration minimal (dh nicht persistence.xml zum Beispiel duplizieren).

scheint meine Unit-Tests zu arbeiten, aber auch wenn meine Unit-Tests transactionnal sein sollte, werden die Daten zwischen den verschiedenen Testverfahren bestanden ...

Hier ist meine Einheit:

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

} 

und meine 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> 

Wenn ich meinen Test ausführen, schlägt test2, weil es 2 Einheit findet, wo ich nur eine erwartete (weil die erste rollbacked haben sollte ...)

ich viele verschiedene Konfigurationen ausprobiert haben und dieser scheint um das umfassendste zu sein, was ich bekommen kann ... Ich habe keine anderen Ideen. Machst du ?

+0

Warum denken Sie, dass die erste Entität zurückgesetzt werden sollte? – janko

+0

Weil ich die Annotation @Transactional verwende, die jeden Testlauf eine eigene Transaktion verwendet, die automatisch von Spring zurückgesetzt wird. – Michel

Antwort

2

Ich schaffte es mit Bitronix anstelle von JOTM arbeiten. Bitronix stellt eine LrcXADataSource bereit, die es einer Nicht-XA-Datenbank ermöglicht, an der JTA-Transaktion teilzunehmen.

Ich denke, die Probleme waren, dass H2 ist nicht XA-konform und die Enhydra StandardXADataSource macht es nicht magisch so (ich endete auch mit HSQLDB, aber das ist nicht mit dem Problem verwandt).

Hier ist meine Feder Kontext, das funktioniert:

<?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

Nur eine Woche, nachdem ich hier gepostet hatte, stieß ich auf das gleiche Problem. Ich konnte JOTM auch nicht richtig zurückrollen lassen; Es hieß immer, es würde zurückrollen, aber die Änderungen aus der Transaktion trafen immer noch die Datenbank. BTM macht den Job gut, sowohl mit einem MySQL als auch mit einem H2-Backend. Seltsam. – Henning

1

Edit: (.. Es scheint, dass ich nur halb wach war, als ich diesen Absatz schrieb Natürlich hast du recht, sollte alles wieder standardmäßig gerollt werden)

könnten Sie überprüfen, welche Transaktionsmanager tut wirklich, zum Beispiel, indem es Debug-Ausgabe dafür aktiviert.

Unter der Annahme, log4j:

log4j.logger.org.springframework.transaction=DEBUG 

Der Transaktionsmanager gibt Ihnen sehr schöne Protokollausgabe über erstellt und schlossen Transaktionen und auch über Commits und Rollbacks. Das sollte Ihnen helfen, herauszufinden, was nicht mit Ihrem Setup funktioniert.

+0

Danke für den Vorschlag. Mehr Protokolle zu haben hat viel geholfen. – Michel

2

Als ich JOTM und Hibernate integrieren wollte, musste ich schließlich meine Implementierung von ConnectionProvider codieren. Hier ist, wie es jetzt aussieht: http://pastebin.com/f78c66e9c

Dann geben Sie Ihre Implementierung als Verbindung privider in Ruhezustand Eigenschaften und Transaktionen beginnen magisch zu arbeiten.

Die Sache ist, dass der Standard-Verbindungsprovider getConnection() auf der Datenquelle aufruft. In Ihrer eigenen Implementierung rufen Sie getXAConnection() auf. GetConnection(). Dies macht den Unterschied

+0

Entschuldigung, ich habe BTM anstelle von JOTM verwendet und ich hatte nicht die Möglichkeit zu tun, was Sie vorschlagen. – Michel

0

hinzufügen @Rollback Anmerkung (von org.springframework.test.annotation), kurz nach der @Transactional Annotation wie im Frühjahr Dokumentation erwähnt.

@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.