1. 概述

Hibernate 中的每个实体对象在其生命周期中都会经历不同的状态:瞬时态(Transient)托管态(Managed)游离态(Detached)删除态(Deleted)

理解这些状态不仅是概念上的要求,更是正确使用 Hibernate 的关键。搞不清状态转换,轻则数据没更新,重则出现脏写或异常,是日常开发中常见的“踩坑”点。

如果你对 savepersistupdatemerge 等方法的使用还拿不准,建议先阅读我们之前关于 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 可能延迟到 flushcommit 时才执行。


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,修改不会同步,需 updatemerge 重新关联
  • 瞬时态:new 出来的对象,需 save 才能持久化
  • 删除态:标记删除,SQL 延迟到提交时执行

✅ 掌握这些状态转换,能避免大多数 Hibernate 使用中的“玄学问题”。

文中所有代码示例均可在 GitHub 获取:https://github.com/tech-tutorial/hibernate-lifecycle-demo


原始标题:Hibernate Entity Lifecycle