Skip to content

Hibernate核心概念

Hibernate 是全自动 ORM 框架,更强调实体对象和数据库表之间的映射。开发者更多操作对象,Hibernate 负责生成 SQL 和维护实体状态。

本页是核心概念摘要。完整的 find -> 持久化上下文 -> 快照 -> 脏检查 -> flush -> SQL -> commit 过程见 Hibernate/JPA 核心全过程原理

核心流程

mermaid
flowchart TD
    A[操作Entity对象] --> B[Session管理状态]
    B --> C[脏检查]
    C --> D[生成SQL]
    D --> E[JDBC执行]
    E --> F[同步数据库]

核心概念

概念说明
Entity实体类,对应数据库表
Session持久化上下文
SessionFactory创建 Session
Transaction事务
HQL面向对象查询语言
Dirty Checking脏检查,自动发现对象变化

实体状态

Hibernate 实体常见状态:

mermaid
stateDiagram-v2
    [*] --> Transient
    Transient --> Persistent: save/persist
    Persistent --> Detached: session关闭
    Detached --> Persistent: merge/update
    Persistent --> Removed: delete

Transient

瞬时状态,对象刚 new 出来,还没有被 Session 管理。

Persistent

持久化状态,对象被 Session 管理。字段变化可能在事务提交时自动同步到数据库。

Detached

游离状态,对象曾经被持久化,但当前不再被 Session 管理。

Removed

删除状态,事务提交时会删除对应数据库记录。

脏检查

脏检查是 Hibernate 的重要能力。持久化状态下的实体被修改后,Hibernate 会在 flush 时比较快照,发现变化后生成 update SQL。

MyBatis和Hibernate对比

对比项MyBatisHibernate
SQL控制开发者手写SQL框架自动生成SQL
学习重点SQL、映射、动态SQL实体状态、关联关系、缓存
灵活性
复杂查询更可控需要优化HQL或原生SQL
适合场景SQL复杂、性能可控CRUD较多、领域模型清晰

使用建议

  1. 理解实体状态再使用 Hibernate。
  2. 复杂查询要关注生成的 SQL。
  3. 避免随意配置双向关联导致查询失控。
  4. N+1 查询要通过 fetch join、批量抓取等方式处理。
  5. 对性能敏感场景要打开 SQL 日志观察实际执行。

Hibernate 和 JPA 的关系

JPA 是规范,Hibernate 是实现。可以这样理解:

mermaid
flowchart TD
    A["JPA 规范"] --> B["EntityManager"]
    A --> C["Entity 注解"]
    A --> D["JPQL"]
    A --> E["持久化上下文概念"]
    F["Hibernate 实现"] --> G["Session"]
    F --> H["脏检查实现"]
    F --> I["SQL 生成"]
    F --> J["懒加载代理"]
    F --> K["缓存和批量优化"]

平时在 Spring Data JPA 中写的是 Repository 和 JPA 注解,但实际运行时很多行为由 Hibernate 完成,例如实体代理、脏检查、SQL 生成、懒加载、N+1、flush 时机。

Session / Persistence Context 是什么

Hibernate 的 Session 可以理解为持久化上下文的具体实现,它在一次事务中管理实体对象。

mermaid
flowchart TD
    A["Session 打开"] --> B["查询或 persist 实体"]
    B --> C["实体进入 Persistence Context"]
    C --> D["保存实体快照"]
    D --> E["业务修改实体"]
    E --> F["flush 脏检查"]
    F --> G["生成 SQL"]
    G --> H["事务提交或回滚"]

Session 中通常有:

  1. 托管实体实例。
  2. 实体快照。
  3. 待执行的插入、更新、删除动作。
  4. 一级缓存。
  5. 懒加载代理需要的上下文。

脏检查完整过程

脏检查不是每次 setter 都立刻发 SQL,而是在 flush 时比较当前状态和快照。

mermaid
flowchart TD
    A["加载实体"] --> B["保存 loadedState 快照"]
    B --> C["业务代码修改字段"]
    C --> D["flush 触发"]
    D --> E["遍历托管实体"]
    E --> F["比较当前值和快照"]
    F --> G{"是否变化"}
    G -- "是" --> H["生成 update SQL"]
    G -- "否" --> I["不更新"]

这解释了两个现象:

  1. 修改托管实体不需要显式 update
  2. 如果实体不在 Session 中,修改不会自动同步。

flush 什么时候发生

常见触发时机:

  1. 事务提交前。
  2. 手动调用 flush()
  3. 某些查询执行前,为保证查询能看到当前事务内变更,可能先 flush。
mermaid
flowchart TD
    A["修改托管实体"] --> B{"触发 flush?"}
    B -- "commit 前" --> C["脏检查并发 SQL"]
    B -- "查询前" --> C
    B -- "手动 flush" --> C
    C --> D["SQL 发到数据库"]
    D --> E{"事务最终结果"}
    E -- "commit" --> F["提交"]
    E -- "rollback" --> G["回滚 SQL 影响"]

flush 后 SQL 已经发出,但事务未必提交,所以仍可回滚。

懒加载代理

Hibernate 为懒加载关联创建代理对象。访问代理属性时,如果 Session 还开着,就发 SQL 加载;如果 Session 已关闭,就报懒加载异常。

mermaid
flowchart TD
    A["查询 Asset"] --> B["department 字段是代理"]
    B --> C{"访问 department.name 时 Session 是否存在"}
    C -- "存在" --> D["发送 SQL 加载 Department"]
    C -- "不存在" --> E["LazyInitializationException"]

为什么不建议 Controller 返回 Entity?

  1. JSON 序列化可能访问懒加载字段。
  2. 双向关联可能循环引用。
  3. 暴露数据库字段。
  4. 序列化过程可能偷偷发 SQL。

N+1 详细示例

实体:

java
@Entity
public class Asset {
    @ManyToOne(fetch = FetchType.LAZY)
    private Department department;
}

代码:

java
List<Asset> assets = assetRepository.findByStatus(1);
for (Asset asset : assets) {
    System.out.println(asset.getDepartment().getDeptName());
}

SQL 可能变成:

text
select * from asset where status = 1;
select * from department where id = ?;
select * from department where id = ?;
select * from department where id = ?;
...

解决方式一:fetch join。

java
@Query("""
    select a
    from Asset a
    join fetch a.department
    where a.status = :status
""")
List<Asset> findWithDepartment(@Param("status") Integer status);

解决方式二:DTO 投影。

java
@Query("""
    select new com.demo.AssetListDTO(a.id, a.assetCode, d.deptName)
    from Asset a
    join a.department d
    where a.status = :status
""")
List<AssetListDTO> findList(@Param("status") Integer status);

列表页更推荐 DTO 投影,因为它只查接口需要的字段。

savepersistmerge 怎么理解

操作作用风险
persist把临时态实体变成托管态,准备插入只能用于新实体
merge把游离对象状态合并到托管对象可能覆盖字段
remove标记删除事务提交时删除
Spring Data save新实体 persist,已有实体 merge要理解新旧判断

merge 的典型坑:

java
Asset asset = new Asset();
asset.setId(1L);
asset.setAssetName("new-name");
entityManager.merge(asset);

如果其他字段都是 null,可能造成不希望的覆盖。更稳妥做法是先查托管实体,再改指定字段。

批量操作为什么容易慢

Hibernate 管理实体状态。如果循环插入 10 万条,持久化上下文会保存大量实体和快照,内存压力很大。

java
for (Asset asset : assets) {
    entityManager.persist(asset);
}

优化:

java
for (int i = 0; i < assets.size(); i++) {
    entityManager.persist(assets.get(i));
    if (i % 500 == 0) {
        entityManager.flush();
        entityManager.clear();
    }
}

flush 把 SQL 发出,clear 清理持久化上下文,避免托管实体越积越多。

如果批量场景非常重,MyBatis、JDBC batch、数据库批量导入工具可能更合适。

商业场景:资产状态更新

不推荐:

java
public void updateAsset(Asset request) {
    assetRepository.save(request);
}

风险:请求对象字段不完整,可能覆盖数据库中已有字段。

推荐:

java
@Transactional
public void checkAsset(Long id) {
    Asset asset = assetRepository.findById(id)
        .orElseThrow(() -> new BizException("资产不存在"));
    asset.check();
}

实体方法:

java
public void check() {
    if (!Objects.equals(this.status, WAIT_CHECK)) {
        throw new BizException("状态不允许审核");
    }
    this.status = CHECKED;
    this.checkedAt = LocalDateTime.now();
}

这样做的好处:

  1. 先拿到托管实体。
  2. 状态规则在实体方法中收敛。
  3. 脏检查只更新真正变化。
  4. 不会被请求对象的 null 覆盖。

线上排查

现象可能原因排查
自动发了很多 SQLN+1 或懒加载打开 SQL 日志,查调用栈
修改实体不落库无事务或游离态检查 @Transactional 和实体状态
接口返回时报错懒加载异常Service 内转 DTO
save 后字段变 nullmerge 覆盖不直接 merge 请求对象
批量导入 OOM持久化上下文堆积分批 flush/clear
分页 count 慢Page 自动 count改 Slice 或优化 count

代码 Demo:实体状态和脏检查

实体类:

java
@Entity
@Table(name = "article")
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;

    public void changeTitle(String title) {
        this.title = title;
    }
}

事务中修改实体:

java
@Transactional
public void renameArticle(Long id, String title) {
    Article article = entityManager.find(Article.class, id);
    article.changeTitle(title);
}

article 被持久化上下文管理,事务提交时 Hibernate 会做脏检查,并生成对应的 update 语句。

为什么会自动更新

Hibernate 的核心原理是持久化上下文会保存托管实体的快照。事务提交时,Hibernate 对比当前对象和快照,如果发现字段变化,就生成对应 SQL。

mermaid
flowchart TD
    A["查询实体"] --> B["进入持久化上下文"]
    B --> C["保存实体快照"]
    C --> D["业务修改字段"]
    D --> E["事务提交时脏检查"]
    E --> F["生成 update SQL"]

如果实体已经 detached,或者修改发生在事务外,就不会自动同步到数据库。这也是很多 Hibernate 问题的根源。

继续深入可以阅读 Hibernate/JPA 核心全过程原理,里面补充了 persistmerge、懒加载、N+1、事务边界、DTO 查询和线上排查流程。

面试标准回答

text
Hibernate 是 JPA 的常见实现,核心是 Session/持久化上下文、实体状态、快照、脏检查、flush 和懒加载。事务内查询或 persist 的实体会进入托管态,Hibernate 保存快照;flush 时比较当前实体和快照,发现变化就生成 SQL。flush 是同步 SQL,commit 是提交事务。懒加载通过代理实现,Session 关闭后访问代理会报 LazyInitializationException。列表循环访问懒加载关联会产生 N+1。生产中要返回 DTO,关注最终 SQL,批量操作要 flush/clear,复杂 SQL 和高性能批处理不一定适合 Hibernate。