2009-10-05 12 views
5

Estoy intentando probar una entidad EJB3 con Spring.Prueba de la unidad Spring/JTA/JPA: Rollback no funciona

El EJB por sí solo no utiliza Spring y me gustaría mantener las duplicaciones de la configuración de JPA de producción como mínimas (es decir, sin duplicar persistence.xml, por ejemplo).

Mis pruebas de unidad parece funcionar, pero a pesar de mis pruebas unitarias deben ser transactionnal, los datos se conserva entre los diversos métodos de ensayo ...

Aquí es mi entidad:

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

} 

Mi prueba de la unidad :

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

} 

y mi 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> 

Cuando ejecuto mi prueba, test2 falla porque encuentra 2 entidad en la que esperaba sólo uno (debido a que el primero de ellos debería haber sido ... rollback)

He probado un montón de diferentes configuraciones y éste parece para ser el más completo que puedo obtener ... No tengo otras ideas. Vos si ?

+0

¿Por qué crees que la primera entidad debería haberse retrotraído? – janko

+0

Porque estoy usando la anotación @Transactional que hace que cada ejecución de prueba use su propia transacción que Spring resuelve automáticamente. – Michel

Respuesta

2

Me las arreglé para hacerlo funcionar usando Bitronix en lugar de JOTM. Bitronix proporciona un LrcXADataSource que permite que una base de datos no XA participe en la transacción JTA.

Creo que los problemas eran que H2 no es compatible con XA y la enhydra StandardXADataSource no lo hace mágicamente (también terminé usando HSQLDB pero eso no está relacionado con el problema).

Aquí es mi contexto de resorte que funciona:

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

Apenas una semana después de publicar aquí, me encontré con el mismo problema. Tampoco pude hacer que JOTM se retrotraiga correctamente; siempre decía que retrocedía, pero los cambios de la transacción aún golpeaban la base de datos. BTM hace bien el trabajo, ambos con MySQL y con un backend H2. Extraño. – Henning

1

Editar: (.. En este momento, parece que sólo estaba medio dormido cuando escribí este párrafo Por supuesto que tienes razón, todo debe ser revertido por defecto)

Usted puede comprobar lo que el administrador de transacciones realmente está funcionando, por ejemplo, habilitando la salida de depuración para ello.

Suponiendo log4j:

log4j.logger.org.springframework.transaction=DEBUG 

El administrador de transacciones le da muy buen resultado de registro sobre las transacciones creadas y se han unido, y también sobre los compromisos y retrotracciones. Eso debería ayudarte a descubrir qué no funciona con tu configuración.

+0

Gracias por la sugerencia. Tener más registros me ayudó mucho. – Michel

2

Cuando estaba tratando de integrar JOTM e Hibernate, finalmente tuve que codificar mi implementación de ConnectionProvider. Esto es lo que parece ahora: http://pastebin.com/f78c66e9c

Luego especifica su implementación ya que el privilegio de conexión en las propiedades de hibernación y las transacciones mágicamente comienzan a funcionar.

El hecho es que el proveedor de conexión predeterminado llama a getConnection() en el origen de datos. En su propia implementación, llama a getXAConnection(). GetConnection(). Esto hace la diferencia

+0

Lo siento, terminé usando BTM en lugar de JOTM y no tuve la oportunidad de hacer lo que sugiere. – Michel

0

Añadir @Rollback anotación (de org.springframework.test.annotation), justo después de la anotación @Transactional como se menciona en la documentación de la 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.