【问题标题】:How to fix 'A rollback database operation after a JUnit test' in Hibernate (without Spring)?如何在 Hibernate(没有 Spring)中修复“JUnit 测试后的回滚数据库操作”?
【发布时间】:2019-05-15 04:21:03
【问题描述】:

我正在处理一个使用 Hibernate (hibernate-entitymanager)、MySQL 连接器 (mysql-connector-java) 和 JUnit (junit-jupiter-engine) 的 Maven 项目,并且我想在 之后执行回滚数据库操作一个 JUnit 测试

好吧,我已经尝试过:

  • 在测试类和测试用例中加入@Transactional注解
  • 创建一个新方法savePerson(Person p, boolean isRollback) 传递一个额外的参数isRollback 并设置一些if 条件。
  • 创建一个Service类PersonServiceImpl,并将@Transactional注解放在顶层类中。

很遗憾,没用。

我有一个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>com.example</groupId>
    <artifactId>entity-manager-07</artifactId>
    <version>1.0</version>
    <name>entity-manager-07</name>
    <description>entity-manager-07</description>

    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.4.2.Final</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.2.Final</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.5.0-M1</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.5.0-M1</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>

Person 实体

package com.example.entities;

import javax.persistence.*;

@Entity
@Table(name = "Person")
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true)
    private Long id;
    @Column(name = "name")
    private String name;
    @Column(name = "lastname")
    private String lastname;

    public Person() {
    }

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLastname() {
        return lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Person [id=");
        builder.append(id);
        builder.append(", name=");
        builder.append(name);
        builder.append(", lastname=");
        builder.append(lastname);
        builder.append("]");
        return builder.toString();
    }

}

一个DAO接口Dao&lt;T&gt;

package com.example.dao;

public interface Dao<T> {

    T save(T t) throws Exception;

    T save(T t, boolean isRollback) throws Exception;
}

还有,DAO 类PersonDAOImpl

package com.example.dao;

import javax.persistence.EntityManager;

import com.example.entities.Person;

public class PersonDAOImpl implements Dao<Person> {

    private EntityManager entityManager;

    public PersonDAOImpl(EntityManager em) {
        this.entityManager = em;
    }

    @Override
    public Person save(Person person) throws Exception {

        try {
            entityManager.getTransaction().begin();

            if (person.getId() == null) {
                entityManager.persist(person);
            }

            entityManager.persist(person);
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            if (entityManager.getTransaction() != null && entityManager.getTransaction().isActive()) {
                entityManager.getTransaction().rollback();
            }
            throw e;
        } finally {
            entityManager.close();
        }

        return person;
    }

    @Override
    public Person save(Person person, boolean isRollback) throws Exception {

        try {
            entityManager.getTransaction().begin();

            if (isRollback){
                entityManager.getTransaction().setRollbackOnly();
            }

            if (person.getId() == null) {
                entityManager.persist(person);
            }

            if (entityManager.getTransaction().getRollbackOnly()){
                System.out.println("Rollback...");
                entityManager.getTransaction().rollback();
            } else {
                entityManager.getTransaction().commit();
            }

        } catch (Exception e) {
            if (entityManager.getTransaction() != null && entityManager.getTransaction().isActive()) {
                entityManager.getTransaction().rollback();
            }
            throw e;
        } finally {
            entityManager.close();
        }

        return person;
    }
}

一个服务接口PersonService

package com.example.services;

import com.example.entities.Person;

public interface PersonService {
    Person savePerson(Person p) throws Exception;
    Person savePerson(Person p, boolean isRollback) throws Exception;
}

一个服务类PersonServiceImpl

package com.example.services;

import javax.persistence.EntityManager;
import javax.transaction.Transactional;

import com.example.dao.PersonDAOImpl;
import com.example.entities.Person;

@Transactional
public class PersonServiceImpl implements PersonService{

    private PersonDAOImpl dao;

    public PersonServiceImpl(EntityManager em) {
        // DAO
        this.dao = new PersonDAOImpl(em);
    }

    @Override
    public Person savePerson(Person p) throws Exception {
        return this.dao.save(p);
    }

    @Override
    public Person savePerson(Person p, boolean isRollback) throws Exception {
        return this.dao.save(p, isRollback);
    }
}

持久性配置

src/test/resources/hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://192.168.1.5:3306/entitymanager-test-001</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">root</property>
        <!-- JDBC Connection Pool Property : -->
        <property name="hibernate.connection.pool_size">2</property>
        <!-- Echo all executed SQL to stdout -->
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.use_sql_comments">true</property>
        <!-- SQL Dialect Property -->
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
        <!-- Drop and re-create the database schema on startup -->
        <property name="hibernate.hbm2ddl.auto">update</property>
        <!-- Set the current session context -->
        <property name="current_session_context_class">thread</property>
        <!-- Entities  -->
        <mapping class="com.example.entities.Person"  />
    </session-factory>
</hibernate-configuration>

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="my-persistence-unit"
        transaction-type="RESOURCE_LOCAL">
        <!-- Persistence provider -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <properties>
            <property name="hibernate.ejb.cfgfile"
                value="./hibernate.cfg.xml" />
            <property name="hibernate.session.events.log" value="true" />
        </properties>
    </persistence-unit>
</persistence>

最后,一个 JUnit 测试PersonServiceImplTest

package services;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.transaction.Transactional;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.example.entities.Person;
import com.example.services.PersonServiceImpl;

@Transactional
class PersonServiceImplTest {

    private EntityManagerFactory emf;
    private EntityManager em;

    @BeforeEach
    void setUp() {
        this.emf = Persistence.createEntityManagerFactory("my-persistence-unit");
    }

    @AfterEach
    void tearDown() {
        emf.close();
    }

    @Test
    @Transactional
    void savePersonRollback() {
        this.em = emf.createEntityManager();

        // Operation
        PersonServiceImpl personService = new PersonServiceImpl(this.em);

        // New person
        Person person = new Person();
        person.setName("Michael");
        person.setLastname("Fisher");

        Person pe = null;
        try {
            pe = personService.savePerson(person, true);
            assertNotNull(pe);
            assertTrue(pe.getId() > 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

新的解决方案尝试(添加时间:2019 年 5 月 17 日)

  • PersonDAOImpl 中添加新属性person
  • 创建一个新方法savePersonDoWork(使用Savepoint、PreparedStatement、Work by Hibernate)
package com.example.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;

import javax.persistence.EntityManager;

import org.hibernate.Session;
import org.hibernate.jdbc.Work;

import com.example.entities.Person;

public class PersonDAOImpl implements Dao<Person> {

    private EntityManager entityManager;
    private Person person;

    public PersonDAOImpl(EntityManager em) {
        this.entityManager = em;
    }

    // ... more code

    @Override
    public Person savePersonDoWork(Person personToSave, boolean isRollback) throws Exception {

        try {
            entityManager.getTransaction().begin();

            if (isRollback) {
                System.out.println("Rollback if...");
                entityManager.getTransaction().setRollbackOnly();
            }

            Session session = entityManager.unwrap(Session.class);
            session.doWork(new Work() {

                @Override
                public void execute(Connection con) throws SQLException {
                    // do something useful
                    con.setAutoCommit(false);

                    // Savepoint
                    Savepoint saveBeforeInsert = con.setSavepoint();

                    // INSERT SQL
                    String sql = "INSERT INTO Person (lastname, name) VALUES (?, ?)";
                    con.prepareStatement(sql);

                    PreparedStatement st = con.prepareStatement(sql);
                    st.setString(1, personToSave.getName());
                    st.setString(2, personToSave.getLastname());

                    // execute the preparedstatement insert
                    Long idPerson = new Long(st.executeUpdate());

                    if (isRollback) {
                        System.out.println("Rollback execute()...");
                        con.rollback(saveBeforeInsert);
                    }

                    // Close
                    st.close();

                    // Set data
                    person = new Person();
                    person.setId(idPerson);
                    person.setName(personToSave.getName());
                    person.setLastname(personToSave.getLastname());

                }
            });

        } catch (Exception e) {
            if (entityManager.getTransaction() != null && entityManager.getTransaction().isActive()) {
                entityManager.getTransaction().rollback();
            }
            throw e;
        } finally {
            entityManager.close();
        }

        return this.person;
    }
}

很遗憾,回滚数据库操作尚未起作用

预期结果

我希望数据库没有任何变化,没有新行,没有新数据,因为我的数据库正在填充测试数据,我不希望这样。

  • 运行测试前SELECT * FROM Person (5 行)
  • 运行测试后SELECT * FROM Person (5 行)

您能帮帮我吗?感谢您的建议、建议或支持。谢谢。

【问题讨论】:

    标签: java hibernate unit-testing jpa rollback


    【解决方案1】:

    你在 DAO 中调用entityManager.getTransaction().commit();。所以事务已经提交,不能再回滚了。

    如果您希望能够回滚事务,则必须将事务处理外部化。

    您使用的是 Spring 或 Java EE,还是只使用简单的 Hibernate 而没有任何其他框架?

    【讨论】:

    • 感谢您的 cmets!好吧,我有 两个 save 方法,在 first method 我不做任何回滚操作,而在 second 我传递一个参数isRollback 因为我想控制何时执行回滚操作。关于您的问题,我使用的是普通的 Hibernate,没有任何其他框架。没有Spring框架可以吗?
    • 您确定您的连接没有处于自动提交模式吗?
    • 好吧,我没有考虑 Hibernate 自动提交配置,谢谢 Simon Martinelli!。我找到并应用了这些行:&lt;property name="hibernate.connection.provider_disables_autocommit"&gt;true&lt;/property&gt;&lt;property name="hibernate.connection.autocommit"&gt;false&lt;/property&gt; 但不幸的是,回滚操作不起作用
    • 但是你看到这个输出了吗? System.out.println("回滚...");
    • 是的,我可以在输出中看到Rollback... 消息,这个my screenshoot
    猜你喜欢
    • 2016-01-11
    • 1970-01-01
    • 1970-01-01
    • 2011-05-09
    • 1970-01-01
    • 1970-01-01
    • 2023-01-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多