Почему сущность Hibernate может быть в состоянии detached и когда это происходит
Что проверяют
Знаете ли вы жизненный цикл сущности и, главное, где проходит граница персистентного контекста. Отсюда растут два самых частых бага: изменения молча не сохраняются и LazyInitializationException.
Как отвечать
У сущности четыре состояния. Transient: объект создан оператором new, идентификатора нет, контекст о нём не знает. Managed: объект в персистентном контексте, и грязная проверка сама превращает изменения полей в UPDATE при сбросе. Removed: помечен к удалению. Detached: идентификатор есть, данные из базы есть, а отслеживания нет.
Detached появляется, когда объект пережил свой контекст:
- закрылась сессия или завершилась транзакция, а сущность вернулась выше по стеку, например из сервиса в контроллер;
- вызвали
detach,evictилиclear; - объект был сериализован и восстановлен, то есть приехал из внешнего кэша или по сети.
Вернуть в контекст можно через merge: он находит или загружает управляемый экземпляр и копирует в него состояние, а переданный аргумент так и остаётся отсоединённым. Специфичный для Hibernate update привязывает сам объект, но падает, если в контексте уже есть сущность с тем же идентификатором.
Практический вывод: за границу транзакции лучше отдавать не сущность, а проекцию или DTO. Тогда вопрос, не отсоединён ли объект, просто не возникает.
Где спотыкаются
Меняют поле после коммита и ждут, что изменение доедет до базы. Грязная проверка работает только для управляемых объектов, отсоединённый меняется в памяти и теряется без единой ошибки.
Второе: зовут merge, игнорируют возвращённое значение и продолжают править прежнюю ссылку, которая так и осталась вне контекста. Работать дальше надо с результатом вызова.
Третье: лечат LazyInitializationException жадной загрузкой всех связей или открытой сессией на время отрисовки. Первое тянет лишние таблицы на каждый запрос, второе держит соединение до конца ответа. Правильнее взять нужное сразу: join fetch, граф сущности или проекция.