Hibernate 5.2.17 не откатывает неудачные транзакции - PullRequest
0 голосов
/ 30 мая 2018

Я сейчас нахожусь в процессе обновления большого проекта с Hibernate 5.1 до Hibernate 5.2.17, и у меня возникла проблема, которую я изо всех сил пытаюсь решить.

У нас есть набортесты, которые тестируют наши DAO с использованием базы данных H2 в памяти, но некоторые тесты начали давать сбой в обновленной версии 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)

Во время исследования я заметил похожую разницу в поведении при попытке удалить также отсоединенную сущность.Мне удалось воссоздать поведение в небольшом автономном проекте, который можно найти здесь .Проект также включает в себя конфигурацию в 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.");
            }
        }
    }
}

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

1 Ответ

0 голосов
/ 30 мая 2018

Ничто в спецификации не говорит о том, что когда вызов 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, поэтому здесь может быть регрессия, но это крайне маловероятно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...