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 项目地址