Contents

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(),那么我们就会从数据库中不必要地加载它的所有数据。