2009-10-05 13 views
5

Sto provando a testare un'entità EJB3 con Spring.Test unità Spring/JTA/JPA: rollback non funzionante

L'EJB stesso non utilizza Spring e vorrei mantenere duplicazioni della configurazione JPA di produzione minima (ad esempio non duplicando persistence.xml per esempio).

mio test di unità sembra funzionare, ma anche se i miei test di unità dovrebbe essere transactionnal, i dati viene mantenuto tra i vari metodi di prova ...

Ecco il mio soggetto:

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

} 

Il mio test di unità :

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

} 

e la mia 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> 

Quando eseguo la mia prova, test2 fallisce perché trova 2 entità dove mi aspettavo un solo (perché il primo avrebbe dovuto essere annullate da una rollback ...)

Ho provato un sacco di diverse configurazioni e questo sembra per essere il più completo che posso ottenere ... Non ho altre idee. Fai ?

+0

Perché pensi che la prima entità avrebbe dovuto essere ripristinata? – janko

+0

Perché sto usando l'annotazione @Transactional che fa in modo che ogni esecuzione di test utilizzi la propria transazione che viene automaticamente ripristinata da Spring. – Michel

risposta

2

Sono riuscito a farlo funzionare utilizzando Bitronix anziché JOTM. Bitronix fornisce un LrcXADataSource che consente a un database non XA di partecipare alla transazione JTA.

Penso che i problemi fossero che H2 non è compatibile con XA e l'Enhydra StandardXADataSource non lo rende magicamente così (ho anche finito di usare HSQLDB ma non è correlato al problema).

Ecco il mio contesto molla che funziona:

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

Solo una settimana dopo aver postato qui, mi sono imbattuto nello stesso problema. Inoltre, JOTM non è riuscito a eseguire correttamente il rollback; ha sempre detto che stava tornando indietro, ma i cambiamenti dalla transazione continuano a colpire il database. BTM fa il lavoro bene, sia con MySQL che con un backend H2. Strano. – Henning

1

Edit: (.. Siamo spiacenti, sembra che io ero solo a metà sveglio quando ho scritto questo paragrafo Naturalmente hai ragione, tutto dovrebbe essere il rollback per impostazione predefinita)

Si potrebbe verificare ciò che il gestore delle transazioni sta davvero facendo, per esempio abilitando l'output di debug per questo.

Assumendo log4j:

log4j.logger.org.springframework.transaction=DEBUG 

Il gestore delle transazioni che si dà molto bella uscita di registro sulle transazioni creati e unito, e anche di commit e rollback. Questo dovrebbe aiutarti a scoprire cosa non funziona con il tuo setup.

+0

Grazie per il suggerimento. Avere più log ha aiutato molto. – Michel

2

Quando stavo cercando di integrare JOTM e Hibernate, alla fine ho dovuto codificare la mia implementazione di ConnectionProvider. Ecco come appare adesso: http://pastebin.com/f78c66e9c

Quindi si specifica l'implementazione come preregolatore di connessione in proprietà di ibernazione e le transazioni iniziano magicamente a funzionare.

Il fatto è che il provider di connessione predefinito chiama getConnection() sull'origine dati. Nella tua stessa implementazione chiami getXAConnection(). GetConnection(). Questo fa la differenza

+0

Scusa, ho finito di usare BTM invece di JOTM e non ho avuto la possibilità di fare ciò che suggerisci. – Michel

0

Aggiungi @Rollback annotazione (da org.springframework.test.annotation), subito dopo l'annotazione @Transactional come indicato nella documentazione di primavera.

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