1. 简介

Hibernate 是一个用于管理持久化数据的强大框架,但其内部工作机制有时并不容易掌握。

在本篇文章中,我们将深入探讨 Hibernate 中对象的三种状态(transient、persistent、detached),以及它们之间的转换方式。同时,我们也会讨论与 detached 对象相关的常见问题及其解决方案。

2. Hibernate 的 Session

Session 接口是与 Hibernate 交互的主要工具。它提供了创建、读取、更新和删除持久化对象的 API。Session 的生命周期很简单:打开 -> 操作 -> 关闭。

当我们对对象进行操作时,这些对象会与当前 Session 绑定。Session 会自动追踪对象的变化,并在事务提交或 Session 关闭时将变更同步到数据库。关闭 Session 后,Hibernate 会断开对象与 Session 的连接。

3. 对象状态

在 Hibernate 的 Session 上下文中,对象可以处于以下三种状态之一:

3.1. Transient(瞬时态)

未被任何 Session 管理的对象即为瞬时态。

这类对象尚未被持久化,数据库中没有对应的记录,也不会被自动保存。

示例代码如下:

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

3.2. Persistent(持久态)

已被 Session 管理的对象即为持久态。

这类对象要么是刚通过 persist()save() 方法保存的,要么是从数据库中加载出来的。

示例代码如下:

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

⚠️ 注意:persist()save() 方法略有不同:

  • persist() 只负责将对象标记为待持久化;
  • save() 不仅会持久化对象,还会返回生成的主键 ID(如果主键是自动生成的话)。

3.3. Detached(游离态)

当 Session 被关闭后,其中的所有对象都会变为游离态。

虽然它们在数据库中有对应的数据行,但已经不再受任何 Session 管理。

示例代码如下:

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

4. 实体的保存与重新绑定

4.1. 保存瞬时态实体

我们新建一个实体并将其保存到数据库。构造对象时,它处于瞬时态。

使用 persist() 方法可以将其变为持久态:

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

接着,如果我们再创建一个具有相同标识符的对象,这个新对象依然是瞬时态。因为它是“新”的,不能直接用 persist() 方法处理。此时应使用 merge() 方法来更新数据库并使其变为持久态:

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

4.2. 保存游离态实体

关闭之前的 Session 后,所有对象都会变为游离态。

这些对象虽然在数据库中有对应记录,但当前未被任何 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);

此时如果尝试更新,Hibernate 会抛出异常:

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.baeldung.states.UserEntity.manager -> com.baeldung.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. 使用级联操作

如果我们正确配置了实体类中的 cascade 属性,嵌套的瞬时态实体也可以被自动持久化:

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

此时当我们持久化父对象时,Hibernate 会自动将操作级联到嵌套实体上:

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

userEntity.setManager(manager); // 将瞬时态 manager 设置给持久态 user
session.saveOrUpdate(userEntity);
transaction.commit();
session.close();

Session otherSession = openSession();
UserEntityWithCascade savedUser = otherSession.get(UserEntityWithCascade.class, "John");
assertThat(savedUser.getManager().getName()).isEqualTo("Adam");

6. 总结

在这篇文章中,我们深入探讨了 Hibernate Session 中对象的三种状态:瞬时态、持久态和游离态,并演示了它们之间的转换方式。同时,我们也介绍了嵌套实体的持久化问题及其解决方案。

如需获取完整源码,请访问 GitHub 项目地址


原始标题:Object States in Hibernate’s Session