1. 概述
Hibernate 中的每个实体对象在其生命周期中都会经历不同的状态:瞬时态(Transient)、托管态(Managed)、游离态(Detached) 和 删除态(Deleted)。
✅ 理解这些状态不仅是概念上的要求,更是正确使用 Hibernate 的关键。搞不清状态转换,轻则数据没更新,重则出现脏写或异常,是日常开发中常见的“踩坑”点。
如果你对 save
、persist
、update
、merge
等方法的使用还拿不准,建议先阅读我们之前关于 Hibernate 持久化方法对比 的文章。
2. 辅助工具方法
本文中会频繁使用几个辅助方法,提升代码可读性:
HibernateLifecycleUtil.getManagedEntities(session)
:获取当前Session
中所有处于托管态的实体DirtyDataInspector.getDirtyEntities()
:获取被标记为“脏数据”的实体列表(即有变更待同步)HibernateLifecycleUtil.queryCount(query)
:执行count(*)
查询,统计数据库中记录数
⚠️ 这些方法均已静态导入,实际实现可参考文末 GitHub 项目。你也可以自己封装类似的工具类用于调试。
3. 核心:持久化上下文(Persistence Context)
在深入实体生命周期之前,必须先搞懂 持久化上下文(Persistence Context)。
简单粗暴地说:它是客户端代码和数据库之间的“中转站”。
- 所有从数据库加载的数据,都会被转换成实体对象,放入这个上下文中
- 所有对实体的修改都会被自动追踪
- 在事务提交时,Hibernate 会自动将变更同步回数据库
从设计模式角度看,持久化上下文是 Unit of Work(工作单元) 模式的实现。JPA 中的 EntityManager
和 Hibernate 的 Session
都是它的具体实现。
📌 本文统一使用 Session
来指代持久化上下文。
而实体的生命周期状态,本质上就是描述该实体与当前 Session
的关系。
4. 托管态(Managed Entity)
托管态实体是数据库某行记录的内存映像,哪怕该记录尚未真正写入数据库(比如刚 save
还没 commit
)。
关键特性:
✅ 被当前 Session
管理
✅ 所有变更都会被自动追踪
✅ 在事务提交或 flush()
时自动同步到数据库
示例场景
我们有一个 FootballPlayer
实体,初始数据如下:
+-------------------+-------+
| Name | ID |
+-------------------+-------+
| Cristiano Ronaldo | 1 |
| Lionel Messi | 2 |
| Gianluigi Buffon | 3 |
+-------------------+-------+
现在想把 ID 为 3 的球员名字从 "Gigi Buffon" 改为全名 "Gianluigi Buffon"。
Session session = sessionFactory.openSession();
Transaction transaction = session.getTransaction();
transaction.begin();
// 初始:Session 中无托管实体
assertThat(getManagedEntities(session)).isEmpty();
// 查询加载实体
List<FootballPlayer> players = session.createQuery("from FootballPlayer").getResultList();
// 此时 Session 已托管 3 个实体
assertThat(getManagedEntities(session)).size().isEqualTo(3);
// 修改名字
FootballPlayer gigiBuffon = players.stream()
.filter(p -> p.getId() == 3)
.findFirst()
.get();
gigiBuffon.setName("Gianluigi Buffon");
transaction.commit();
// 提交后,脏数据检查器应捕获变更
assertThat(getDirtyEntities()).size().isEqualTo(1);
assertThat(getDirtyEntities().get(0).getName()).isEqualTo("Gianluigi Buffon");
4.1 原理剖析
- 一旦实体被
Session
加载,就进入托管态 - 任何属性变更都会被
Session
的“脏检查机制”(dirty checking)捕获 - 无需手动调用
update()
,提交事务时自动同步
⚠️ 注意:托管态实体一定有主键(ID),哪怕对应数据库记录还没插入(INSERT 延迟到 flush 时)。
5. 游离态(Detached Entity)
游离态实体就是一个普通的 POJO 对象,它有主键,但不再被任何 Session
管理。
何时变成游离态?
Session
被关闭- 显式调用
session.evict(entity)
或session.clear()
示例代码
FootballPlayer cr7 = session.get(FootballPlayer.class, 1L);
assertThat(getManagedEntities(session)).size().isEqualTo(1);
// 将其踢出管理
session.evict(cr7);
assertThat(getManagedEntities(session)).isEmpty();
// 修改游离态实体
cr7.setName("CR7");
transaction.commit();
// ❌ 不会触发任何 SQL,因为不再被追踪
assertThat(getDirtyEntities()).isEmpty();
如何重新关联?
使用 session.update()
或 session.merge()
可将其重新纳入管理:
FootballPlayer messi = session.get(FootballPlayer.class, 2L);
session.evict(messi);
messi.setName("Leo Messi");
transaction.commit();
assertThat(getDirtyEntities()).isEmpty(); // ❌ 无变更
transaction = startTransaction(session);
session.update(messi); // 重新托管
transaction.commit();
assertThat(getDirtyEntities()).size().isEqualTo(1); // ✅ 变更被同步
5.1 主键是唯一标识
看这个例子:
FootballPlayer gigi = new FootballPlayer();
gigi.setId(3);
gigi.setName("Gigi the Legend");
session.update(gigi);
虽然 gigi
是新创建的对象,但因为设置了 ID=3,session.update()
会认为它对应数据库中 ID 为 3 的记录,从而将其视为游离态实体并重新托管。
📌 这在 Web 应用中很常见:前端表单提交后,后端直接构造对象并调用 update
。
⚠️ 但要注意:如果你只设置了部分字段(如只设了 name),其他字段会是 null,可能导致意外覆盖。建议先查后改,或使用 merge
更安全。
6. 瞬时态(Transient Entity)
瞬时态实体是没有主键、也不被任何 Session
管理的对象。
典型场景:通过 new
关键字创建的实体。
要让它变成持久化数据,必须调用 save()
或 saveOrUpdate()
:
FootballPlayer neymar = new FootballPlayer();
neymar.setName("Neymar");
session.save(neymar);
// 此时已分配 ID,且被 Session 托管
assertThat(neymar.getId()).isNotNull();
assertThat(getManagedEntities(session)).size().isEqualTo(1);
// 但数据库还查不到(事务未提交)
int count = queryCount("select count(*) from Football_Player where name='Neymar'");
assertThat(count).isEqualTo(0);
transaction.commit();
// 提交后,INSERT 执行,数据落地
count = queryCount("select count(*) from Football_Player where name='Neymar'");
assertThat(count).isEqualTo(1);
📌 save()
一执行,对象就变成托管态,ID 也被分配(取决于生成策略),但 SQL 可能延迟到 flush
或 commit
时才执行。
7. 删除态(Deleted Entity)
当调用 session.delete(entity)
后,实体进入删除态。
- 实体仍存在于
Session
的缓存中 - 状态标记为
DELETED
- 实际
DELETE
SQL 在事务提交时执行
session.delete(neymar);
assertThat(getManagedEntities(session).get(0).getStatus()).isEqualTo(Status.DELETED);
⚠️ 注意:即使调用了 delete
,在事务提交前你仍能访问该实体对象,但它已被标记为“待删除”。
8. 总结
- 持久化上下文是核心,它决定了实体的状态和行为
- 托管态:自动追踪变更,无需手动 update
- 游离态:脱离 Session,修改不会同步,需
update
或merge
重新关联 - 瞬时态:new 出来的对象,需
save
才能持久化 - 删除态:标记删除,SQL 延迟到提交时执行
✅ 掌握这些状态转换,能避免大多数 Hibernate 使用中的“玄学问题”。
文中所有代码示例均可在 GitHub 获取:https://github.com/tech-tutorial/hibernate-lifecycle-demo