Hibernate 5.2.17没有回滚失败的事务

时间:2018-05-30 14:33:26

标签: java hibernate

我目前正在将大型项目从Hibernate 5.1更新到Hibernate 5.2.17,我遇到了一个我正在努力解决的问题。

我们有一套使用H2内存数据库测试DAO的测试,但是在更新版本的Hibernate上,一些测试已经开始失败。

某些测试尝试从持久性上下文中删除null实体,并期望操作失败并显示IllegalArgumentException。使用新版本的Hibernate时,异常仍然按预期抛出,但事务不再被回滚并且处于活动状态,因此导致后续测试失败,因为已经存在活动事务。堆栈跟踪包括在下面:

java.lang.AssertionError: Transaction is still active when it should have been rolled back.
    at org.junit.Assert.fail(Assert.java:88)
    at hibernatetest.persistence.HibernateTestDAOTest.testDeleteDetachedEntity(HibernateTestDAOTest.java:50)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)

在调查时,我注意到在尝试删除分离的实体时,行为也存在类似的差异。我已经能够在一个可以找到here的小型独立项目中重新创建行为。该项目还包括pom.xml中的配置(注释掉),用于针对Hibernate 5.0.10运行,其中测试通过时没有问题,并且失败的事务被正确回滚。

虽然我无法重新创建删除null实体的错误,但我已设法使用分离的实体重新创建它,我希望能解决这个问题的原因将有助于指导我为什么它在实际代码中也失败null

我们在这里做错了什么,或者这是Hibernate本身的问题?

代码也包含在下面:

的pom.xml

<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>HibernateTest</groupId>
    <artifactId>HibernateTest</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <maven.compiler.version>3.7.0</maven.compiler.version>

        <!-- Uncomment this property to run as Hibernate 5.0.10 -->
        <!-- <hibernate.core.version>5.0.10.Final</hibernate.core.version> -->
        <!-- Uncomment this property to run as Hibernate 5.2.17 -->
        <hibernate.core.version>5.2.17.Final</hibernate.core.version>
        <junit.version>4.12</junit.version>
        <h2.version>1.4.197</h2.version>
        <javaee.api.version>7.0</javaee.api.version>

    </properties>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.compiler.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.core.version}</version>
        </dependency>
        <!-- Uncomment these dependencies to run using Hibernate 5.0.10 -->
        <!-- 
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-java8</artifactId>
            <version>${hibernate.core.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.core.version}</version>
            <scope>test</scope>
        </dependency>
        -->
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>${javaee.api.version}</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

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

HibernateTest.java (实体类):

package hibernatetest.persistence;

import java.util.UUID;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.Type;

@Entity
@Table(name = "hibernate_test")
public class HibernateTest {

    @Id
    @Column(name = "id")
    @Type(type = "uuid-char")
    private UUID id;

    public HibernateTest(final UUID id) {
        this.id = id;
    }
}

HibernateTestDAO.java

package hibernatetest.persistence;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public class HibernateTestDAO {

    @PersistenceContext(unitName = "hibernate-test")
    private EntityManager entityManager;

    public void delete(final HibernateTest entity) {
        entityManager.remove(entity);
    }
}

EntityManagerRule.java (JUnit规则为测试提供实体管理器):

package hibernatetest.persistence;

import java.lang.reflect.Field;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import org.junit.rules.ExternalResource;

public class EntityManagerRule extends ExternalResource {

    private EntityManagerFactory emFactory;

    private EntityManager em;

    @Override
    protected void before() {
        emFactory = Persistence.createEntityManagerFactory("hibernate-test");
        em = emFactory.createEntityManager();
    }

    @Override
    protected void after() {
        if (em != null) {
            em.close();
        }
        if (emFactory != null) {
            emFactory.close();
        }
    }

    public HibernateTestDAO initDAO() {
        final HibernateTestDAO dao = new HibernateTestDAO();

        try {
            injectEntityManager(dao);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return dao;
    }

    public EntityManager getEntityManager() {
        return em;
    }

    public void persist(final Object entity) {
        final EntityTransaction transaction = em.getTransaction();
        transaction.begin();
        try {
            em.persist(entity);
        } catch (Exception e) {
            transaction.rollback();
            throw e;
        }
        transaction.commit();
    }

    private void injectEntityManager(final HibernateTestDAO dao) throws Exception {
        final Field emField = dao.getClass().getDeclaredField("entityManager");
        emField.setAccessible(true);
        emField.set(dao, em);
    }
}

HibernateTestDAOTest.java

package hibernatetest.persistence;

import static org.junit.Assert.fail;

import java.util.UUID;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

public class HibernateTestDAOTest {

    @Rule
    public EntityManagerRule rule = new EntityManagerRule();

    private HibernateTestDAO dao;

    @Before
    public void setup() {
        dao = rule.initDAO();
    }

    @Test
    public void testDeleteNullEntity() {
        HibernateTest entity = null;
        try {
            dao.delete(entity);
        } catch (IllegalArgumentException e) {
            if (rule.getEntityManager().getTransaction().isActive()) {
                fail("Transaction is still active when it should have been rolled back.");
            }
        }
    }

    @Test
    public void testDeleteDetachedEntity() {
        HibernateTest entity = new HibernateTest(UUID.randomUUID());
        rule.persist(entity);
        rule.getEntityManager().detach(entity);
        try {
            dao.delete(entity);
        } catch (IllegalArgumentException e) {
            if (rule.getEntityManager().getTransaction().isActive()) {
                fail("Transaction is still active when it should have been rolled back.");
            }
        }
    }
}
来自src/test/resources/META-INF

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_2_0.xsd"
    version="2.0">
    <persistence-unit name="hibernate-test" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <class>hibernatetest.persistence.HibernateTest</class>

        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.url"
                value="jdbc:h2:mem:test;INIT=create schema if not exists test\;runscript from 'classpath:/populate.sql';DB_CLOSE_DELAY=-1;"/>
            <property name="javax.persistence.validation.mode" value="none"/>
        </properties>
    </persistence-unit>
</persistence>
来自src/test/resources

populate.sql

CREATE TABLE IF NOT EXISTS hibernate_test (
  id       UUID NOT NULL
);

1 个答案:

答案 0 :(得分:1)

规范中没有任何内容表明当对EntityManager#remove的调用失败时,持久性提供程序应该回滚现有事务,这没有任何意义。

如果您查看Hibernate测试套件中的所有示例,您会注意到这种行为:

EntityManager entityManager = getOrCreateEntityManager();
try {
  entityManager.getTransaction().begin();
  // do something
  entityManager.getTransaction().commit();
}
catch ( Exception e ) {
  if ( entityManager != null && entityManager.getTransaction.isActive() ) {
    entityManager.getTransaction().rollback();
  }
  throw e;
}
finally {
  if ( entityManager != null ) {
    entityManager.close();
  }
}

如果你的测试以前工作过,而且不再以同样的方式工作,我不确定我是否必须说这是一个错误,因为你上面提供的代码不符合我在这里展示的正确处理用户代码中的回滚,除非你有弹簧或其他一些你没有说明的框架。

但如果您觉得5.1和5.2之间存在回归,欢迎您打开JIRA并使用可重复的测试用例进行报告,我们可以进一步调查。

需要记住的一个关键点是5.2.x引入了JPA工件hibernate-entitymanager合并到hibernate-core中,因此可能会出现回归,但极不可能。