java - 如何在 Hibernate(没有 Spring)中修复“JUnit 测试后的回滚数据库操作”?
问题描述
我正在使用 Hibernate ( hibernate-entitymanager
)、MySQL 连接器 (mysql-connector-java) 和 JUnit ( junit-jupiter-engine
) 处理 Maven 项目,并且我想在 JUnit 测试后执行回滚数据库操作
好吧,我已经尝试过:
@Transactional
在测试类和测试用例中放注解- 创建一个新方法
savePerson(Person p, boolean isRollback)
传递一个额外的参数isRollback
并设置一些if
条件。 - 创建一个服务类
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<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 日)
person
在中添加新属性PersonDAOImpl
- 创建一个新方法
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行)
你能帮我吗?我会感谢您的意见、建议或支持。谢谢。
解决方案
您正在调用entityManager.getTransaction().commit();
DAO。所以事务已经提交,不能再回滚了。
如果您希望能够回滚事务,则必须将事务处理外部化。
您使用的是 Spring 或 Java EE,还是只使用没有任何其他框架的普通 Hibernate?
推荐阅读
- ruby-on-rails - 是否可以使用 Wisper 将一个订阅应用于多个侦听器?
- r - 识别具有变量所有缺失值的组并在 R 中的组内推断
- excel - 如果偏移量相同,则根据以下值填充空单元格
- java - java -version 不想加载我的 Java_home 的新值
- reactjs - 满足条件后如何取消地图?
- python - 创建小型聊天机器人时出现 rasa_nlu 错误
- python - 是否可以从一个文件到另一个文件使用 Django 模板块?
- react-native - 我怎样才能在需要反应原生时制作一个选择器
- angular - 如何在订阅 Angular 中更好地编写订阅?
- pandas - Pandas:如何在多索引时间序列中填充缺失的期间/日期时间值?