Contents

Hibernate 会话中的对象状态

1. 简介

Hibernate 是一个用于管理持久数据的便捷框架,但有时了解它的内部工作原理可能会很棘手。

在本教程中,我们将了解对象状态以及如何在它们之间移动。我们还将研究分离实体可能遇到的问题以及如何解决这些问题。

2. Hibernate 的会话

*Session *接口是用来与 Hibernate 进行通信的主要工具。它提供了一个 API,使我们能够创建、读取、更新和删除持久对象。Session具有简单的生命周期。我们打开它,执行一些操作,然后关闭它。

当我们在Session期间对对象进行操作时,它们会附加到该Session。我们所做的更改会在关闭时被检测并保存。关闭后,Hibernate 会断开对象和会话之间的连接。

3. 对象状态

在 Hibernate 的Session上下文中,对象可以处于三种可能的状态之一:瞬态、persist或分离。

3.1. transient

**我们没有附加到任何Session的对象处于transient。**由于它从未被持久化,因此它在数据库中没有任何表示。因为没有Session知道它,所以它不会自动保存。

让我们使用构造函数创建一个用户对象并确认它不是由会话管理的:

Session session = openSession();
UserEntity userEntity = new UserEntity("John");
assertThat(session.contains(userEntity)).isFalse();

3.2. persist

**我们与Session关联的对象处于持久状态。**我们要么保存它,要么从持久性上下文中读取它,因此它代表数据库中的某些行。

让我们创建一个对象,然后使用persist方法使其持久化:

Session session = openSession();
UserEntity userEntity = new UserEntity("John");
session.persist(userEntity);
assertThat(session.contains(userEntity)).isTrue();

或者,我们可以使用save方法。不同之处在于persist方法只会保存一个对象,如果需要,save方法会另外生成它的标识符。

3.3. detached

当我们关闭session时,其中的所有对象都将被分离。尽管它们仍然代表数据库中的行,但它们不再由任何session管理:

session.persist(userEntity);
session.close();
assertThat(session.isOpen()).isFalse();
assertThatThrownBy(() -> session.contains(userEntity));

接下来,我们将学习如何保存瞬态和分离的实体。

4. 保存和重新附加实体

4.1. 保存transient 实体

让我们创建一个新实体并将其保存到数据库中。当我们第一次构造对象时,它会处于transient 。

为了persist化我们的新实体,我们将使用persist方法:

UserEntity userEntity = new UserEntity("John");
session.persist(userEntity);

现在,我们将创建另一个与第一个具有相同标识符的对象。第二个对象是瞬态的,因为它还没有被任何session管理,但是我们不能使用persist方法使其持久化。它已经在数据库中表示,因此在持久层的上下文中它并不是真正的新事物。

相反,我们将使用merge方法来更新数据库并使对象持久化

UserEntity onceAgainJohn = new UserEntity("John");
session.merge(onceAgainJohn);

4.2. 保存detached实体

**如果我们关闭前一个session,我们的对象将处于detached状态。**与前面的示例类似,它们在数据库中表示,但它们当前不受任何session管理。我们可以使用merge方法使它们再次持久化:

UserEntity userEntity = new UserEntity("John");
session.persist(userEntity);
session.close();
session.merge(userEntity);

5. 嵌套实体

当我们考虑嵌套实体时,事情变得更加复杂。假设我们的用户实体还将存储有关他的经理的信息:

public class UserEntity {
    @Id
    private String name;
    @ManyToOne
    private UserEntity manager;
}

当我们保存这个实体时,我们不仅需要考虑实体本身的状态,还要考虑嵌套实体的状态。让我们创建一个持久的用户实体,然后设置它的管理器:

UserEntity userEntity = new UserEntity("John");
session.persist(userEntity);
UserEntity manager = new UserEntity("Adam");
userEntity.setManager(manager);

如果我们现在尝试更新它,我们会得到一个异常:

assertThatThrownBy(() -> {
            session.saveOrUpdate(userEntity);
            transaction.commit();
});
java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.blogdemo.states.UserEntity.manager -> com.blogdemo.states.UserEntity

发生这种情况是因为 Hibernate 不知道如何处理瞬态嵌套实体。

5.1. 持久化嵌套实体

解决此问题的一种方法是显式保留嵌套实体:

UserEntity manager = new UserEntity("Adam");
session.persist(manager);
userEntity.setManager(manager);

然后,在提交事务后,我们将能够检索正确保存的实体:

transaction.commit();
session.close();
Session otherSession = openSession();
UserEntity savedUser = otherSession.get(UserEntity.class, "John");
assertThat(savedUser.getManager().getName()).isEqualTo("Adam");

5.2. 级联操作

如果我们在实体类中正确配置关系的级联属性,瞬态嵌套实体可以自动持久化:

@ManyToOne(cascade = CascadeType.PERSIST)
private UserEntity manager;

现在,当我们持久化对象时,该操作将级联到所有嵌套实体:

UserEntityWithCascade userEntity = new UserEntityWithCascade("John");
session.persist(userEntity);
UserEntityWithCascade manager = new UserEntityWithCascade("Adam");
userEntity.setManager(manager); // add transient manager to persistent user
session.saveOrUpdate(userEntity);
transaction.commit();
session.close();
Session otherSession = openSession();
UserEntityWithCascade savedUser = otherSession.get(UserEntityWithCascade.class, "John");
assertThat(savedUser.getManager().getName()).isEqualTo("Adam");