«1. Обзор

В этом уроке мы увидим, что такое прокси в контексте метода load() Hibernate.

Читателям, плохо знакомым с Hibernate, рекомендуется сначала ознакомиться с основами.

2. Краткое введение в прокси и метод load()

По определению, прокси — это «функция, уполномоченная действовать в качестве заместителя или замены другой».

Это применимо к Hibernate, когда мы вызываем Session.load() для создания так называемого неинициализированного прокси нашего желаемого класса сущности.

Проще говоря, Hibernate создает подклассы нашего класса сущностей, используя библиотеку CGLib. Помимо метода @Id, реализация прокси-сервера делегирует все другие методы свойств сеансу Hibernate для заполнения экземпляра, примерно так:

public class HibernateProxy extends MyEntity {
    private MyEntity target;

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

Этот подкласс будет возвращен вместо прямого запроса к базе данных.

Как только вызывается один из методов объекта, объект загружается и в этот момент становится инициализированным прокси.

3. Прокси и ленивая загрузка

3.1. Отдельный объект

Давайте подумаем о сотруднике как о объекте. Для начала предположим, что она не имеет отношения ни к каким другим таблицам.

Если мы используем Session.load() для создания экземпляра Сотрудника:

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

Тогда Hibernate создаст неинициализированный прокси Сотрудника. Он будет содержать идентификатор, который мы ему дали, но в остальном не будет иметь других значений, потому что мы еще не попали в базу данных.

Однако, как только мы вызовем метод для albert:

String firstName = albert.getFirstName();

Тогда Hibernate запросит в таблице базы данных сотрудников сущность с первичным ключом 1, заполнив albert его свойствами из соответствующей строки.

Если не удается найти строку, Hibernate генерирует исключение ObjectNotFoundException.

3.2. Отношения «один ко многим»

Теперь давайте также создадим объект Company, где у компании много сотрудников:

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 теперь будет инициализирован, и фактически, рабочее место теперь будет быть установлен как неинициализированный прокси в зависимости от стратегии выборки.

4. Ленивая загрузка

Теперь функция load() не всегда дает нам неинициализированный прокси. На самом деле документ Session java напоминает нам (выделено мной):

This method might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed.

Простой пример того, когда это может произойти, — это размер пакета.

Предположим, что мы используем @BatchSize для нашего объекта Employee:

@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));

Если мы вызовем getFirstName для Екатерины:

String cathy = catherine.getFirstName();

Тогда , на самом деле Hibernate может решить загрузить сразу всех трех сотрудников, превратив всех троих в инициализированные прокси.

И затем, когда мы вызываем имя Даррелла:

String darrell = darrell.getFirstName();

Тогда Hibernate вообще не обращается к базе данных.

5. Нетерпеливая загрузка

5.1. Использование get()

Мы также можем полностью обойти прокси и попросить Hibernate загрузить реальную вещь, используя Session.get():

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

Это вызовет базу данных сразу, вместо того, чтобы возвращать прокси.

И на самом деле, вместо ObjectNotFoundException, он вернет null, если finnigan не существует.

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);

Поскольку мы знаем, что в этой ситуации мы собираемся изменить только запись о сотруднике, вызов load() для Company имеет смысл.

Если бы мы вызвали get() для Company, мы бы без необходимости загрузили все ее данные из базы данных.

6. Заключение

В этой статье мы вкратце узнали, как работают прокси-серверы Hibernate и как это влияет на метод загрузки объектов и их взаимосвязей.

Кроме того, мы кратко рассмотрели, чем load() отличается от get().

«Как обычно, полный исходный код, сопровождающий руководство, доступен на GitHub.