java - Hibernate 5.2.17 不回滚失败的事务

标签 java hibernate

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

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

某些测试尝试从持久性上下文中删除 null 实体,并期望操作失败并出现 IllegalArgumentException。在新版本的 Hibernate 中,异常仍然会按预期抛出,但事务不再回滚并保持 Activity 状态,因此导致后续测试失败,因为已经有一个 Activity 事务。堆栈跟踪如下:

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

最佳答案

规范中没有任何内容表明,当对 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();
  }
}

如果您的测试以前有效并且不再以相同的方式进行,我不确定我是否一定会说这是一个错误,因为您上面提供的代码不符合我在此处显示的内容正确处理用户代码中的回滚,除非您有 spring 或其他未说明的框架在起作用。

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

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

关于java - Hibernate 5.2.17 不回滚失败的事务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50607352/

相关文章:

java - 运行 jar 包应用程序时出现 Hibernate InvalidMappingException

java - 使用 OneToMany 将对象从 JSON 保存到数据库

java - Hibernate 标准 - org.hibernate.QueryException : could not resolve property:

java - jboss 7.0.1 hibernate javax.persistence.PersistenceException : No Persistence provider for EntityManager

java - 玩!删除时的框架参照完整性约束

java - 记录 Spring REST API

java - Apache Hadoop API 以原子方式创建唯一目录

java - 保存时上传?

JAVA——抽象

java - 图着色算法(贪心着色)