В чём разница между методами get() и load() в Hibernate?
get() сразу идёт в БД и возвращает null если запись не найдена; load() возвращает прокси и делает SELECT только при обращении к полям, бросая ObjectNotFoundException при отсутствии записи.
get() и load() в Hibernate: ключевые различия
Оба метода позволяют получить объект из базы данных по первичному ключу, но работают принципиально по-разному.
Метод get()
Session.get(Class, id) немедленно выполняет SELECT в базу данных. Если запись не найдена — возвращает null. Никакой прокси не создаётся.
Session session = sessionFactory.openSession();
Employee emp = session.get(Employee.class, 42L);
if (emp == null) {
System.out.println("Сотрудник не найден");
} else {
System.out.println(emp.getName());
}
Метод load()
Session.load(Class, id) возвращает прокси-объект (Javassist/ByteBuddy) без обращения к БД. SQL выполняется только при первом обращении к полю, отличному от идентификатора. Если записи не существует — при инициализации прокси выбрасывается ObjectNotFoundException.
Session session = sessionFactory.openSession();
// SQL ещё не выполнялся
Employee empProxy = session.load(Employee.class, 99L);
// SQL выполняется здесь; если записи нет — ObjectNotFoundException
System.out.println(empProxy.getName());
Сравнительная таблица
- Обращение к БД: get() — немедленно; load() — отложено (lazy).
- Если запись не найдена: get() — возвращает null; load() — бросает ObjectNotFoundException.
- Тип возвращаемого объекта: get() — реальный экземпляр сущности; load() — прокси-объект.
- Первичный кэш (L1): оба сначала смотрят в кэш первого уровня.
- Сессия закрыта до инициализации прокси: load() бросает LazyInitializationException.
Когда использовать load()
Типичный сценарий — назначение связи без реальной загрузки данных. Например, при создании записи нужно выставить внешний ключ, зная только идентификатор:
// Не делаем лишний SELECT — просто устанавливаем связь
Department dept = session.load(Department.class, deptId);
Employee emp = new Employee();
emp.setName("Ivan");
emp.setDepartment(dept); // INSERT с FK, без SELECT department
session.persist(emp);
Современная альтернатива (JPA)
В JPA-стиле вместо get() используется EntityManager.find(), вместо load() — EntityManager.getReference(). Поведение аналогично.
// find — аналог get()
Employee e = em.find(Employee.class, 42L); // null если не найден
// getReference — аналог load()
Department dRef = em.getReference(Department.class, 10L); // прокси
Подводные камни
- LazyInitializationException: прокси, полученный через
load(), нельзя инициализировать после закрытия сессии — доступ за пределами транзакции приведёт к исключению. - ObjectNotFoundException vs null: если код ожидает
null, а используетload(), исключение появится в неожиданном месте — далеко от места вызова. - instanceOf и конкретный тип: прокси — это подкласс сущности, поэтому
proxy.getClass() != Employee.class; используйтеHibernate.getClass(entity)илиinstanceof. - equals()/hashCode() на прокси: если реализация опирается на
getClass(), сравнение прокси с реальным объектом даётfalse. - Кэш первого уровня: если объект уже загружен в сессию,
load()вернёт не прокси, а реальный объект — поведение меняется в зависимости от порядка вызовов. - Устаревший API:
Session.load()иSession.get()помечены как deprecated в Hibernate 6 — предпочтительны JPA-методыfind()иgetReference().
Common mistakes
- Путать термин «get vs load» с соседним механизмом Hibernate.
- Не называть границу lifecycle, transaction, thread или request для «get vs load».
- Игнорировать production-эффекты «get vs load»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «get vs load» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «get vs load».
- Уточнить, какие настройки или API меняют «get vs load» в реальном сервисе.