Где персистентный контекст хранит информацию об изменяемых сущностях?
Что проверяют
Понимаете ли вы, как Hibernate узнаёт об изменениях без явного save(), и во что это обходится по памяти. Следом спрашивают про dirty checking, про момент flush и про то, почему пакетная обработка миллиона строк падает с OutOfMemoryError.
Как отвечать
Коротко: в памяти приложения, внутри объекта сессии. Персистентный контекст в Hibernate реализован классом StatefulPersistenceContext, который живёт в SessionImpl (он же EntityManager в терминах JPA). Внутри:
- карта сущностей: по ключу
EntityKey(тип сущности плюс идентификатор) лежит сам управляемый объект. Это и есть кеш первого уровня: повторныйfind()с тем же id в рамках сессии вернёт тот же экземпляр без запроса в базу; EntityEntry: запись на каждую сущность со статусом, версией иloadedState, снимком значений полей на момент загрузки;- dirty checking: при
flushHibernate сравнивает текущие поля сloadedStateи для отличающихся сущностей генерируетUPDATE.flushпроисходит перед коммитом и, в режимеFlushMode.AUTO, перед запросами, результат которых зависит от несброшенных изменений; - bytecode enhancement: с включённым dirty tracking сущность сама помечает изменённые поля, и полное сравнение со снимком не нужно.
Время жизни контекста совпадает с сессией. В Spring это по умолчанию транзакция (@PersistenceContext с типом TRANSACTION), а при spring.jpa.open-in-view, который в Spring Boot включён по умолчанию, сессия живёт до конца HTTP-запроса. Назовите и состояния сущности: new, managed, detached, removed. Изменения отслеживаются только у managed.
Где спотыкаются
Путают персистентный контекст с кешем второго уровня. Второй уровень общий для фабрики сессий и опционален, а снимки для dirty checking хранятся только в первом.
Забывают, что каждая загруженная сущность держит в памяти себя и свой снимок. В пакетных задачах нужны периодические flush() и clear(), а для чтения больших выборок подходит read-only режим, в котором снимки не создаются.
Меняют detached-объект и ждут UPDATE. После закрытия сессии изменения никто не отслеживает, нужен merge(), который скопирует состояние в управляемый экземпляр.