Hibernate 中代理简介
1. 概述
在本教程中,我们将了解在 Hibernate 的*load()*方法的上下文中代理是什么。
对于刚接触 Hibernate 的读者,请考虑先熟悉 基础知识 。
2. Proxies 和*load()*方法简介
根据定义,proxy 是“被授权充当代理人或替代他人的职能”。
当我们调用*Session.load()*来创建 我们想要的实体类的未初始化代理时,这适用于 Hibernate 。
简单地说,Hibernate 使用 CGLib库继承了我们的实体类。除了*@Id*方法之外,代理实现将所有其他属性方法委托给 Hibernate 会话以填充实例,有点像:
public class HibernateProxy extends MyEntity {
private MyEntity target;
public String getFirstName() {
if (target == null) {
target = readFromDatabase();
}
return target.getFirstName();
}
}
这个子类将被返回,而不是直接查询数据库。
一旦调用了其中一个实体方法,该实体就会被加载,并在此时成为一个初始化的代理。
3. 代理和延迟load
3.1. 单一实体
让我们将 Employee视为一个实体。首先,我们假设它与任何其他表都没有关系。
如果我们使用 Session.load()实例化一个Employee:
Employee albert = session.load(Employee.class, new Long(1));
然后 Hibernate 将创建一个未初始化的Employee代理。它将包含我们给它的 ID,否则将没有其他值,因为我们还没有访问数据库。
但是,一旦我们在albert上调用方法 :
String firstName = albert.getFirstName();
然后 Hibernate 将在Employee数据库表中查询主键为 1 的实体,并 用对应行中的属性填充albert。
如果找不到行,那么 Hibernate 会抛出 ObjectNotFoundException。
3.2. 一对多关系
现在,让我们也创建一个 Company实体,其中 Company有许多Employee:
public class Company {
private String name;
private Set<Employee> employees;
}
如果我们这次在公司上使用 Session.load():
Company bizco = session.load(Company.class, new Long(1));
String name = bizco.getName();
然后像以前一样填充公司的属性,只是员工集略有不同。
看,我们只查询了公司行,但代理将不理会员工集,直到我们 根据获取策略调用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()*不会总是给我们一个未初始化的代理。事实上,Session java doc 提醒我们(强调补充):
当访问非标识符方法时,此方法可能会返回按需初始化的代理实例。 何时发生这种情况的一个简单示例是批量大小。 假设我们 在Employee实体上 使用*@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));
如果我们 在 catherine上调用getFirstName:
String cathy = catherine.getFirstName();
然后,实际上,Hibernate 可能决定一次加载所有三个员工,将所有三个员工都变成初始化代理。
然后,当我们调用 darrell的名字时:
String darrell = darrell.getFirstName();
然后Hibernate 根本不会访问数据库。
5. 立即加载
5.1. 使用get()
我们也可以完全绕过代理并要求 Hibernate 使用*Session.get()*加载真实的东西:
Employee finnigan = session.get(Employee.class, new Long(6));
这将立即调用数据库,而不是返回代理。
实际上,如果 finnigan不存在,它将返回null而不是ObjectNotFoundException。
5.2. 性能影响
虽然*get()很方便,但load()*在数据库上可以更轻松。
例如,假设Gerald要去一家新公司工作:
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);
由于我们知道在这种情况下我们只会更改Employee记录,因此为Company调用 *load()*是明智的。
如果我们 在 Company上调用get(),那么我们就会从数据库中不必要地加载它的所有数据。