We had a production incident that resulted in a bunch of threads deadlocking and the server stopped working. To try and investigate, I tested some stuff with different spring transactional propagations, and if I'm not mistaken, the REQUIRES_NEW propagation will start two connections if there is no existing transaction at all. Is this correct?? I tried googling, but found no information about this.
I made a test. Here's a sample class:
package test;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
#Service
public class TheService {
#Transactional(propagation=Propagation.REQUIRES_NEW)
public void doSomething() {
System.out.println("Here I am doing something.");
}
}
Here is the unit test that I made:
package test;
import javax.annotation.Resource;
import org.junit.Test;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
#ContextConfiguration(locations = {"classpath:test.xml"})
public class TheServiceTest extends AbstractTransactionalJUnit4SpringContextTests {
#Resource
private TheService theService;
#Test
public void test() {
theService.doSomething();
}
}
And last but not least, here is my test xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd"
default-autowire="byName">
<context:component-scan base-package="test" />
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="operator.entityManagerFactory" />
</bean>
<bean id="operator.entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="operatorPersistenceUnit" />
<property name="dataSource" ref="operator.dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false" />
<property name="generateDdl" value="true" />
<property name="databasePlatform" value="org.hibernate.dialect.H2Dialect" />
</bean>
</property>
</bean>
<bean id="operator.dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:operator" />
<property name="username" value="sa" />
<property name="password" value="" />
<property name="maxActive" value="1" /> <!-- NOTE -->
</bean>
</beans>
The reason why I want REQUIRES_NEW for a method is because it is vital to not get any dirty reads from it, and it can be executed from both within another transaction and outside.
If I keep the maxActive property at 1, this test will deadlock and never print anything. If I change it to 2 however, the test will go through.
The reason why this is a concern is that even if I set maxActive to a much higher value, with enough threads waiting to execute this method, they can all end up occupy one connection each and wait for a second one.
Have I done something wrong? Have I misunderstood anything?
I appreciate any help! Thanks!
It has nothing to do with propagation=REQUIRES_NEW that will NOT open 2 connections by default. The issue is that you are extending AbstractTransactionalJUnit4SpringContextTests.
As your test case extends AbstractTransactionalJUnit4SpringContextTests which, as you can see, is #Transactional. This transaction, for tests, is managed by the TransactionalTestExecutionListener.
So what happens is when you start your test, before the execution of the test method a transaction is started by the test framework. Next you invoke your service which starts another transaction due to being annotated with #Transactional(propagation=REQUIRES_NEW).
The fix is quite easy don't extend AbstractTransactionalJUnit4SpringContextTests and just annotate your class with #RunWith(SpringRunner.class).
#RunWith(SpringRunner.class)
#ContextConfiguration(locations = {"classpath:test.xml"})
public class TheServiceTest { ... }
Related
I am using simple "helloWorld"ish application to learn Spring,Hibernate and transaction management using AOP. But is not working as expected. I am getting exception in transaction management. Details as follows :-
Spring version 4.3.8
Hibernate version 5.2.10
HSQL DB version 2.3.4
Spring.xml look as follows
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<!-- Enable Annotation based Declarative Transaction Management -->
<tx:annotation-driven proxy-target-class="true" mode="aspectj"
transaction-manager="transactionManager" />
<!-- THIS IS COMMENTED. Without commenting same result. I TRIED USING HibernateTransactionManager. still got same result. -->
<!--
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean> -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:file:C:/ProjectRelated/softwares/hsqldb-2.3.4/hsqldb/data/FirstFile"/>
<property name="username" value="sa"/>
<property name="password" value="sys"/>
</bean>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" >
<array>
<value>com.kaushik.winnersoft.data</value>
</array>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
</props>
</property>
</bean>
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
<bean id="customerDAO" class="com.kaushik.winnersoft.dao.CustomerDAOImpl">
<property name="hibernateTemplate" ref="hibernateTemplate"></property>
</bean>
<bean id="customerManager" class="com.kaushik.winnersoft.CustomerManagerImpl">
<property name="customerDAO" ref="customerDAO"></property>
</bean>
DAOImpl class is
public class CustomerDAOImpl implements CustomerDAO {
private HibernateTemplate hibernateTemplate;
public HibernateTemplate getHibernateTemplate() {
return hibernateTemplate;
}
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
#Override
#Transactional
public void create(Customer customer) {
System.out.println("in dao creating");
hibernateTemplate.save(customer);
System.out.println("in dao creating done");
}
I get output as follows
Doing
in dao creating
Exception in thread "main" org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.
at org.springframework.orm.hibernate5.HibernateTemplate.checkWriteOperationAllowed(HibernateTemplate.java:1165)
at org.springframework.orm.hibernate5.HibernateTemplate$12.doInHibernate(HibernateTemplate.java:643)
at org.springframework.orm.hibernate5.HibernateTemplate$12.doInHibernate(HibernateTemplate.java:640)
at org.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:359)
at org.springframework.orm.hibernate5.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:326)
at org.springframework.orm.hibernate5.HibernateTemplate.save(HibernateTemplate.java:640)
at com.kaushik.winnersoft.dao.CustomerDAOImpl.create(CustomerDAOImpl.java:27)
at com.kaushik.winnersoft.CustomerManagerImpl.createCustomer(CustomerManagerImpl.java:20)
at com.kaushik.winnersoft.SpringTest.main(SpringTest.java:14)
Answer
Based on the coments given below by M. Denium; I did following changes and it worked.
1) Used HibernateTransactionManager instead of DataSourceTransactionManager
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory" />
</bean>
2) In removed mode="aspectj"
so it looks like
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" />
I see to problems with your configuration
You are not using the proper PlatformTransactionManager
Unless you are using load- or compile time weaving I doubt the mode="aspectj" is really correct.
First you need to use the PlatformTransactionManager that supports your persistence technology, as you are using Hibernate 5, you need to use the org.springframework.orm.hibernate5.HibernateTransactionManager instead of the DataSourceTransactionManager. (the latter is for application that only do plain JDBC transactions).
From the <tx:annotation-driven /> remove the mode="aspectj" as I suspect you are actually not using full blown AspectJ but are relying on plain Spring to do this for you.
Pro Tip: Instead of using HibernateTemplate, which isn't recommended anymore since Hibernate 3.0.1, just use plain SessionFactory with getCurrentSession instead. This will allow you to write plain Hibernate daos. As suggested in the reference guide.
I create the configuration of Spring + JPA/Hibernate/c3p0 on this way:
Spring-Servlet.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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:component-scan base-package="com.nassoft.erpweb"/>
<mvc:resources mapping="/resources/**" location="/WEB-INF/resources/" />
<mvc:annotation-driven />
<mvc:interceptors>
<bean class="com.nassoft.erpweb.login.interceptor.AuthenticatorInterceptor" />
</mvc:interceptors>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="packagesToScan" value="com.nassoft.erpweb.*" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
</props>
</property>
</bean>
<bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/nsm_erp" />
<property name="user" value="root" />
<property name="password" value="1234" />
<property name="minPoolSize" value="5" />
<property name="maxPoolSize" value="20" />
<property name="maxStatements" value="50" />
<property name="idleConnectionTestPeriod" value="3000" />
<property name="loginTimeout" value="300" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven />
<bean id="persistenceExceptionTranslationPostProcessor" class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
</beans>
I'm not using persistence.xml because I read in some places its not necessary in Spring 4 with Hibernate.
When I start the server it still loading and don't start in 45s (nor 180s) in Tomcat7.
I create a factory of EntityManager to use in my project:
package com.nassoft.erpweb.factory;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceUnit;
public class ConnectionFactory {
#PersistenceUnit
private static EntityManagerFactory entityManagerFactory;
public static EntityManager getEntityManager(){
if (entityManagerFactory == null){
entityManagerFactory = Persistence.createEntityManagerFactory("ERPWeb");
}
return entityManagerFactory.createEntityManager();
}
}
I think my configuration is not correct, but I don't found any places with a good text about it.
Can someone help-me?
Edited.
Problem solved!
First: I applied Dependency Injection in each controller to bring the DAOs with IoC.
Second: I use the annotation #Repository to create a repository in each DAO that will receive my databases methods.
Third: I created the EntityManage in this way for each DAO:
#PersistenceContext
private EntityManager manager;
This is not a complete answer. I"m just pointing you to a direction.
Spring cannot find the ConnectionFactory class of yours, so it will not inject the entityManagerFactory. Its not required for you to again create a singleton for passing the entityManager, so no ConnectionFactory class is required. Spring will do it for you by injecting into the DAO or Controller etc., for example you have the following DAO that gets the data.
#Service
public class SomeDAO {
#AutoWire -- i'm not sure what you call for the entityManager.
private static EntityManagerFactory entityManagerFactory;
}
There is more info here. Instead of #autowiring he is using manual injection. I your case ,you can try with autowiring.
Also make sure that you have these classes in the <spring:component-scan /> path of the application context file or else the spring wont be able to recognize and inject the entity manager.
So after a big refactoring project, I am left with this exception and am unsure as how to correct it. It's dealing with some code that I did not write and I am unfamiliar with how it all works. There are other questions out there dealing with this exception, but none seem to fit my situation.
The class which uses EntityManager is SpecialClaimsCaseRepositoryImpl:
package com.redacted.sch.repository.jpa;
//Imports
#Repository
public class SpecialClaimsCaseRepositoryImpl extends SimpleJpaRepository<SpecialClaimsCaseDto, SpecialClaimsCaseDto.Id> implements SpecialClaimsCaseRepository{
#PersistenceContext(unitName = "schManager")
private EntityManager em;
//Some autogenerated methods
public void setEntityManager(EntityManager em) {
this.em = em;
}
public EntityManager getEntityManager() {
return em;
}
}
Persistence.xml:
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="schManager">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>jdbc/SCH_DS</jta-data-source>
<class>com.redacted.sch.domain.model.SpecialClaimsCaseDto</class>
<properties>
<property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.WebSphereExtendedJTATransactionLookup" />
<property name="hibernate.cache.region.factory_class" value="net.sf.ehcache.hibernate.SingletonEhCacheRegionFactory" />
<property name="hibernate.cache.use_query_cache" value="true" />
<property name="hibernate.cache.use_second_level_cache" value="true" />
<property name="hibernate.dialect" value="com.bcbsks.hibernate.dialect.DB2Dialect" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.generate_statistics" value="false" />
<property name="hibernate.jdbc.use_scrollable_resultset" value="true" />
</properties>
</persistence-unit>
</persistence>
sch_model_spring.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.redacted.repository.jpa,
com.redacted.sch.domain.model,
com.redacted.sch.repository.jpa,
com.redacted.sch.service,
com.redacted.sch.service.impl"/>
<tx:annotation-driven />
<tx:jta-transaction-manager />
<!-- Data source used for testing -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.ibm.db2.jcc.DB2Driver" />
<property name="url" value="jdbc:db2:redacted.redacted.com" />
<property name="username" value="redacted" />
<property name="password" value="redacted" />
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="schManager" />
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
</beans>
And here's my project structure:
>
Here's a portion of the stack trace, with the full trace at this fpaste
Caused by: java.lang.IllegalStateException: A JTA EntityManager cannot use getTransaction()
at org.hibernate.ejb.AbstractEntityManagerImpl.getTransaction(AbstractEntityManagerImpl.java:985)
at org.springframework.orm.jpa.DefaultJpaDialect.beginTransaction(DefaultJpaDialect.java:67)
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:380)
... 80 more
I'm a total noob here, so if any other information is needed just ask and I'll update.
Thanks for all the help!
The problem is your configuration. You have hibernate configured for JTA.
<property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.WebSphereExtendedJTATransactionLookup" />
Whereas you are using local transactions instead of distributed transactions.
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:380)
You have 2 possible solutions
remove the JpaTransactionManager and replace it with a JTA transaction manager
remove the remove the hibernate.transaction.manager_lookup_class from the hibernate settings.
If you don't really need distributed transactions option 2 is the easiest, if you need distributed transactions simply adding <tx:jta-transaction-manager /> will setup a proper JTA tx manager for your environment. Remove the definition for the JpaTransactionManager.
Update:
Your configuration is flawed in 2 ways.
Your EntityManager configuration already contains a jndi lookup for the datasource, which you override in your applicationContext by configuring a local datasource
You have both a <tx:jta-transaction-manager /> and JpaTransactionManager which one do you want to use? At the moment the latter is overriding the first one.
Create 2 seperate configurations one for local testing and one for production using JTA en JNDI lookups. (Preferable your testing code only overrides the beans necessary).
Use WebSphereTransactionManagerLookup for the transaction manager lookup in Hibernate
<property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.WebSphereTransactionManagerLookup" />
and remove your current transaction manager and replace it with the WebSphereUowTransactionManager.
<tx:annotation-driven/>
<bean id="transactionManager" class="org.springframework.transaction.jta.WebSphereUowTransactionManager"/>
for your transaction manager lookup in Spring.
See IBM Websphere and Spring docs
for more in depth documentation.
I am using Hibernate / Spring / Maven / MySQL and unit tests with JUnit. Up to yesterday, my testdata persisted in the database even after the test run was completed. I configured the hell out of this day and all of the sudden all data are being deleted after every test run. Quite sure, this is no bug, but a config issue. Nevertheless, I am lost.
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util">
<tx:annotation-driven/>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springbyexample.util.log.AnnotationLoggerBeanPostProcessor" />
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>classpath:/settings.properties</value>
</property>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="persistenceUnitName" value="RDBMS"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="false"/>
<property name="showSql" value="true"/>
<property name="databasePlatform" value="${databasePlatformDialect}"/>
<property name="database">
<util:constant static-field="${databaseVendor}" />
</property>
</bean>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<context:component-scan base-package="de.test">
<context:exclude-filter type="regex" expression="de\.sandbox\.test\.hibernatedao.*"/>
</context:component-scan>
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
</beans>
persistence.xml:
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="RDBMS" transaction-type="RESOURCE_LOCAL">
<exclude-unlisted-classes>true</exclude-unlisted-classes>
</persistence-unit>
</persistence>
Thanks for suggestions.
EDIT ----
As demanded, the testcase:
package de.test.base;
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("/appContextMain.xml")
#Transactional
public abstract class SpringTestCase {
}
Child:
package de.test.dao;
import de.test.base.SpringTestCase;
import de.test.businessobjects.BodSt;
import de.test.businessobjects.Trainee;
import junit.framework.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.springbyexample.util.log.AutowiredLogger;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
import java.util.List;
public class BodStDAOTest extends SpringTestCase {
#AutowiredLogger
final Logger logger = null;
#Autowired
private IBodStDAO bodStDAO;
#Autowired
private ITraineeDAO traineeDAO;
Trainee trainee = new Trainee();
#Before
public void onSetUpInTransaction() throws Exception {
this.trainee.setName("Name");
this.trainee.setSurname("Surname");
this.trainee = this.traineeDAO.save(this.trainee);
}
#Test
public void testSingleObjectSave() throws Exception {
Collection before = (List) this.bodStDAO.getAll();
BodSt bodSt = new BodSt();
bodSt.setWeight((float) 2.2);
bodSt.setHeight(new Float(0.0));
bodSt.setTrainee(trainee);
bodSt = this.bodStDAO.save(bodSt);
Collection after = (List) this.bodStDAO.getAll();
this.logger.info("BodSt size before: " + before.size() + " and after: " + after.size());
Assert.assertEquals(before.size() + 1, after.size());
}
}
Using #Rollback(value = false) annotation on your test case that creates the test data makes sure that the data doesnt get deleted.
Could you be running your tests inside a transaction and rollbacking it?
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 ?
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
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>
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.
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.