Search code examples
mysqlspringhibernateh2transactional

Spring tests - problems with rollback


I have created Spring integration test in my application. The problem is, one test doesn't rollback properly, leaves some stuff in database and causes subsequent tests to fail.

I have noticed, that tests works good if persisted entity is simple entity. The test fails, when entity is part of inheritance hierarchy and inheritance type is of type InheritanceType.JOINED. When I change it to InheritanceType.SINGLE_TABLE it desn't fail.

Below is the code: Test class:

 @RunWith(SpringJUnit4ClassRunner.class)
 @Transactional
 @ContextConfiguration(locations = {"classpath:beans/repository-beans.xml"})
 public class OnlyParentRollbackProblemSpringTest {
@PersistenceContext
private EntityManager entityManager;

@Test
//PASSES
public void isDBEmptyTest_1() throws Exception {
    Query countQuery = entityManager.createQuery("select count(qi) from " + ParentEntity.class.getSimpleName() + " qi");
    int elementsCount = ((Long)countQuery.getSingleResult()).intValue();
    assertThat(elementsCount).isEqualTo(0);
}

@Test
//PASSES
public void testThatInfluencesOtherTests() {
    ParentEntity queueItem = new ParentEntity();
    entityManager.persist(queueItem);
    ParentEntity queueItem1 = new ParentEntity();
    entityManager.persist(queueItem1);
    // given
    flush();
    // when
    Query deleteQuery = entityManager.createQuery("delete from " + ParentEntity.class.getSimpleName());
    deleteQuery.executeUpdate();
    flush();
    // then
    Query countQuery = entityManager.createQuery("select count(qi) from " + ParentEntity.class.getSimpleName() + " qi");
    int elementsCount = ((Long)countQuery.getSingleResult()).intValue();
    assertThat(elementsCount).isEqualTo(0);
}

@Test
//FAILS
public void isDBEmptyTest_2() throws Exception {
    Query countQuery = entityManager.createQuery("select count(qi) from " + ParentEntity.class.getSimpleName() + " qi");
    int elementsCount = ((Long)countQuery.getSingleResult()).intValue();
    assertThat(elementsCount).isEqualTo(0);
}

private void flush() {
    entityManager.flush();
    entityManager.clear();
}
 } 

spring configuration:

<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">


<context:component-scan base-package="no.mintra.offlinetrainingportal.infrastructure.persistence" />

<bean
    class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

<bean
    class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

<tx:annotation-driven transaction-manager="transactionManager" />

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
    <property name="dataSource" ref="H2DataSource" />
</bean>


<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml" />
    <property name="dataSource" ref="H2DataSource" />
    <property name="jpaVendorAdapter" ref="H2JpaVendorAdaptor" />
    <property name="jpaPropertyMap">
        <map>
            <entry key="hibernate.hbm2ddl.auto" value="create-drop" />
        </map>
    </property>
</bean>

<bean id="H2DataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="org.h2.Driver"/>
    <property name="url" value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=MYSQL;LOCK_MODE=3"/>
</bean>

<bean id="H2JpaVendorAdaptor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <property name="showSql" value="true" />
    <property name="generateDdl" value="true" />
    <property name="databasePlatform" value="org.hibernate.dialect.H2Dialect" />
</bean>

Entities:

@javax.persistence.Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class ParentEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

public ParentEntity() {
}

public Integer getId() {
    return id;
}

public void setId(Integer id) {
    this.id = id;
}
} 

@Entity
public class SecondChildEntity extends ParentEntity {
@Column(unique = true)
@NotNull
private Integer userId;

public SecondChildEntity(Integer userId) {
    this.userId = userId;
}

public Integer getUserId() {
    return userId;
}
}

And pom.xml file:

<project xmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>test</groupId>
<artifactId>test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>test</name>
<url>http://maven.apache.org</url>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <junit.version>4.8.1</junit.version>
    <org.springframework.version>3.1.0.RELEASE</org.springframework.version>
    <log4j.version>1.2.15</log4j.version>
    <org.slf4j.version>1.5.8</org.slf4j.version>
    <hibernate-core.version>3.6.8.Final</hibernate-core.version>
    <hibernate-validator.version>4.1.0.Final</hibernate-validator.version>
    <commons-dbcp.version>1.2.2</commons-dbcp.version>
    <h2database.version>1.3.163</h2database.version>
    <commons-collections.version>3.2.1</commons-collections.version>
    <javax.servlet.version>2.5</javax.servlet.version>
    <javax.validation.version>1.0.0.GA</javax.validation.version>
    <fest.version>1.2</fest.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <encoding>UTF-8</encoding>
                <source>1.6</source>
                <target>1.6</target>
            </configuration>
        </plugin>
    </plugins>
</build>

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>${h2database.version}</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.easytesting</groupId>
        <artifactId>fest-assert</artifactId>
        <version>${fest.version}</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
        <exclusions>
            <exclusion>
                <groupId>com.sun.jmx</groupId>
                <artifactId>jmxri</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${org.slf4j.version}</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${org.slf4j.version}</version>
    </dependency>


    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>${hibernate-validator.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${org.springframework.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${org.springframework.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>${org.springframework.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${org.springframework.version}</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>${org.springframework.version}</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.hibernate</groupId>
                <artifactId>ejb3-persistence</artifactId>
            </exclusion>
        </exclusions>
        <version>${hibernate-core.version}</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>${hibernate-core.version}</version>
    </dependency>

</dependencies>


Solution

  • This issue is specific to H2 DBMS.

    H2Dialect contains the following warning:

    if ( !( majorVersion > 1 || minorVersion > 2 || buildId >= 139 ) ) {
        log.warn(
            "The {} version of H2 implements temporary table creation such that it commits " +
            "current transaction; multi-table, bulk hql/jpaql will not work properly",
            ( majorVersion + "." + minorVersion + "." + buildId )
        );
    }
    

    You face this issue because executing bulk delete against entity with JOINED inheritance involves creation of temporary table (see generated SQL), so that the first part of transaction gets committed.

    As a workaround you can use native SQL queries instead of bulk HQL delete query. Or just use another in-memory database such as HSQLDB.