HibernateMiddleTechnical

В чём разница между методами 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» в реальном сервисе.

Sources

Related topics