Reputation: 2474
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">
<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 ?
Upvotes: 5
Views: 13386
Reputation: 157
Add @Rollback annotation (from org.springframework.test.annotation), just after the @Transactional annotation as mentioned in the spring documentation.
@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.
Upvotes: 0
Reputation: 2474
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">
<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>
Upvotes: 2
Reputation: 9371
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
Upvotes: 2
Reputation: 16311
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.
Upvotes: 1