java - Hibernate 5.2.17 不回滚失败的事务
问题描述
我目前正在将一个大型项目从 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)
在进行调查时,我注意到在尝试删除分离实体时也存在类似的行为差异。我已经能够在一个小的独立项目中重新创建该行为,该项目可以在此处找到。该项目还包括针对 Hibernate 5.0.10 运行的 pom.xml(已注释掉)中的配置,其中测试通过没有问题并且失败的事务被正确回滚。
虽然我无法重新创建删除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 Rule):
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.");
}
}
}
}
persistence.xml来自src/test/resources/META-INF
:
<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>
populate.sql来自src/test/resources
:
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
因此这里可能会出现回归,但这种可能性极小。
推荐阅读
- ssl - 由于 ssl 握手终止,分布式模式下的 Jmeter 无法到达从节点
- image - 有没有办法像 fitbod 应用程序恢复模型一样选择图像/图标的某个部分并更改颜色
- jsonpath - Jsonpath 以子字段的条件获取父字段信息
- vba - 使用 vba 打开一个文件夹,以便我可以在 Access 2016 中粘贴图像。我有非工作选项
- c++ - “错误:
不能超载”(但我不想这样做!) - oracle-sqldeveloper - “dbupgrade”的执行返回“0x1”错误的非零状态
- apache-kafka - 如何将 prestoDB 中的数据插入 Kafka 主题?
- angular - 如果我在 KendoGridAddCommand 下有两个不同的添加按钮,如何检查我点击了哪一个
- facebook - 突然评论插件代码没有显示评论框
- python - ValueError:DataFrame 构造函数在尝试从管道调用数据时未正确调用错误