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 投影,因为它只查接口需要的字段。

save、persist、merge 怎么理解 ​

操作作用风险
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 核心全过程原理,里面补充了 persist、merge、懒加载、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。