1. 概述

在本篇文章中,我们将深入探讨 Hibernate 中 load() 方法所涉及的代理(Proxy)机制。

如果你对 Hibernate 还不熟悉,建议先了解下 Hibernate 基础知识

2. 代理与 load() 方法简介

代理(Proxy)的定义是:“被授权代替另一个实体执行功能的对象”。

在 Hibernate 中,当我们调用 Session.load() 方法时,Hibernate 会创建一个我们实体类的 未初始化代理(uninitialized proxy)对象。

简单来说,Hibernate 会使用 CGLib 库来生成我们实体类的子类。除了主键(@Id)字段外,其他所有属性的访问都会被代理拦截,并委托给 Hibernate Session 来从数据库中加载数据。例如:

public class HibernateProxy extends MyEntity {
    private MyEntity target;

    public String getFirstName() {
        if (target == null) {
            target = readFromDatabase();
        }
        return target.getFirstName();
    }
}

这个子类代理对象会代替原始实体返回,而不是直接查询数据库

当我们调用代理对象的任意方法时,Hibernate 才会真正去数据库加载数据,此时代理变为 已初始化 状态。

3. 代理与懒加载

3.1. 单个实体

Employee 实体为例,假设它不关联任何其他表:

Employee albert = session.load(Employee.class, new Long(1));

此时 Hibernate 会返回一个未初始化的代理对象。✅ 这个代理只包含我们传入的 ID,其他字段都是空的,因为还没有访问数据库

当我们调用:

String firstName = albert.getFirstName();

Hibernate 才会查询数据库中主键为 1 的记录,并将数据填充到 albert 对象中。

❌ 如果找不到该记录,Hibernate 会抛出 ObjectNotFoundException

3.2. 一对多关系

现在我们引入一个 Company 实体,一个公司可以有多个员工:

public class Company {
    private String name;
    private Set<Employee> employees;
}

当我们使用 load() 加载公司:

Company bizco = session.load(Company.class, new Long(1));
String name = bizco.getName();

✅ 公司的基本属性会被加载,但员工集合 employees 不会被立即加载,直到我们调用 getEmployees() 时,才根据抓取策略决定是否加载

3.3. 多对一关系

反向也是一样:

public class Employee {
    private String firstName;
    private Company workplace;
}

当我们使用 load() 获取员工:

Employee bob = session.load(Employee.class, new Long(2));
String firstName = bob.getFirstName();

bob 对象会被初始化,但 workplace 属性此时是一个 未初始化的代理对象,具体是否加载取决于抓取策略。

4. 懒加载的“变种”

⚠️ 注意,load() 并不总是返回未初始化的代理。Hibernate 的 Session 文档 提到:

该方法 可能 返回一个按需初始化的代理对象,当访问非主键方法时才会触发初始化。

举个例子,使用 @BatchSize 注解时:

@Entity
@BatchSize(size=5)
class Employee {
    // ...
}

假设我们加载了三个员工:

Employee catherine = session.load(Employee.class, new Long(3));
Employee darrell = session.load(Employee.class, new Long(4));
Employee emma = session.load(Employee.class, new Long(5));

当我们调用:

String cathy = catherine.getFirstName();

✅ Hibernate 可能会一次性加载这三个员工,将它们全部初始化

再调用:

String darrell = darrell.getFirstName();

此时 ✅ Hibernate 不会再次访问数据库,因为数据已经加载过了。

5. 急加载

5.1. 使用 get() 方法

我们可以完全绕过代理机制,使用 Session.get() 直接获取真实对象:

Employee finnigan = session.get(Employee.class, new Long(6));

✅ 这种方式会 立即访问数据库,不会返回代理对象。

⚠️ 如果记录不存在,get() 会返回 null,而 load() 会抛出异常。

5.2. 性能影响

虽然 get() 更直观,但 load() 在某些场景下性能更优。

例如,假设我们要为员工分配新公司:

Employee gerald = session.get(Employee.class, new Long(7));
Company worldco = (Company) session.load(Company.class, new Long(2));
employee.setCompany(worldco);        
session.save(employee);

因为我们只是要设置外键,不需要加载 Company 的完整数据,✅ 使用 load() 是明智之举。

⚠️ 如果使用 get(),就会 不必要地加载整个公司数据,浪费资源。

6. 小结

在这篇文章中,我们简单介绍了 Hibernate 中代理的工作机制,以及它如何影响 load() 方法在实体和关系中的行为。

还快速对比了 load()get() 的区别。

完整代码示例可从 GitHub 获取:GitHub 项目地址


原始标题:Proxy in Hibernate load() Method