Что происходит с коллекцией в Hibernate при вызове hashCode внутри транзакции, если entity является ключом с переопределенными equals и hashCode
Что проверяют
Знаете ли вы, что у сущности hashCode обязан быть стабильным на всём жизненном цикле, и понимаете ли, почему сгенерированный базой идентификатор для этого не годится. Вопрос сразу про две темы: контракт equals и hashCode и состояния сущности.
Как отвечать
- Сценарий, о котором спрашивают: объект кладут в
HashSetили в ключиHashMap, пока он ещё новый иidравенnull. Хеш считается поnull, объект попадает в свою корзину. Дальше внутри транзакции проходитpersistиflush, база выдаёт идентификатор, Hibernate проставляет его в поле, иhashCodeтого же объекта становится другим. - Коллекция об этом не узнает: элемент остался в корзине, выбранной по старому хешу.
containsиremoveвозвращают отрицательный ответ: обходом объект виден, а достать или удалить его нельзя. С ключомHashMapто же самое: значение лежит, по ключу не находится. - Отсюда правило:
hashCodeне должен зависеть ни от изменяемых полей, ни от сгенерированного идентификатора. Рабочие варианты: константа от класса, естественный ключ из предметной области илиUUID, присвоенный в конструкторе до сохранения.equalsпри этом может сравнивать поidс проверкой наnull. - Ленивая загрузка добавляет свой слой: у прокси поля пустые, пока не сработал геттер. Поэтому в
equalsиhashCodeобращаются через геттеры, а тип сверяют черезinstanceof, иначеgetClass()у прокси не совпадёт с классом сущности.
Где спотыкаются
Советуют учитывать в equals и hashCode все поля, как для обычного объекта переноса данных. Для сущности это прямой путь к описанной поломке: любое изменение поля внутри транзакции переставляет объект относительно корзин, и коллекция его теряет.
Второе: приплетают кэш Hibernate, будто из-за неверного hashCode в нём окажутся устаревшие данные. Кэш первого уровня адресует сущности по имени типа и идентификатору, а не через equals, и с этой ошибкой не связан вовсе.
Третье: не учитывают, что hashCode сущности может потянуть за собой ленивую коллекцию, а вне сессии вылетит LazyInitializationException. Особенно неприятно со связью на Set сущностей: чтобы её собрать, Hibernate позовёт hashCode у каждого элемента.