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 项目地址。