首页 > 解决方案 > 如何在 Hibernate(没有 Spring)中修复“JUnit 测试后的回滚数据库操作”?

问题描述

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

好吧,我已经尝试过:

不幸的是,它没有用。

我有一个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<T>

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

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

不幸的是,回滚数据库操作还没有工作

预期成绩

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

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

标签: javahibernateunit-testingjparollback

解决方案


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

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

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


推荐阅读