views:

3219

answers:

3

I am trying to test an entity EJB3 with Spring.

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

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 :

<?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"&gt;

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

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 ?

+1  A: 

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

Henning
Thanks for the suggestion. Having more logs helped a lot.
Michel
+2  A: 

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

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

artemb
Sorry I ended using BTM instead of JOTM and I did not had the chance to do what you suggest.
Michel
+2  A: 

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

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

Here is my spring context that works :

<?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"&gt;

    <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>
Michel
Just a week after posting here, I ran into the same problem. I also couldn't get JOTM to rollback properly; it always said it was rolling back, but the changes from the transaction still hit the database. BTM does the job just fine, both with a MySQL and with a H2 backend. Weird.
Henning