Какие проблемы могут возникнуть при использовании entity в качестве ключа в Map с переопределением equals и hashCode по всем полям
Что проверяют
Понимаете ли вы контракт equals/hashCode и то, как он сталкивается с жизненным циклом JPA-сущности. Следом просят показать, как вы сами реализовали бы эти методы для entity.
Как отвечать
HashMap кладёт ключ в корзину по hashCode() в момент вставки и больше его не пересчитывает. Если хеш потом изменился, get() ищет в другой корзине: запись есть в Map, но найти и удалить её нельзя. У сущности с equals/hashCode по всем полям хеш меняется постоянно:
- генерация id: до
persist()у сущностиid == null, после него Hibernate проставляет сгенерированное значение, и хеш уже другой. Сущность, положенная вHashSetдо сохранения, в нём теряется; - изменяемые поля: любой сеттер меняет хеш, а сущности существуют, чтобы их меняли;
- ленивые связи: если в
hashCodeпопала@OneToMany-коллекция, вызов либо тянет её из базы, либо бросаетLazyInitializationExceptionвне сессии; - двунаправленные связи:
Orderсчитает хеш поitems,Itemпоorder, и выходитStackOverflowError; - прокси Hibernate: сравнение через
getClass()ломается, когда один объект прокси, а другой реальная сущность. Поля у прокси не инициализированы, читать их нужно через геттеры.
Как делают правильно. Если есть неизменяемый бизнес-ключ (номер договора, email под @NaturalId), equals и hashCode строятся по нему. Если есть только сгенерированный id, популярен вариант: equals сравнивает id, когда он не null, а hashCode возвращает константу для класса. Хеш стабилен весь жизненный цикл, цена в том, что в большой хеш-коллекции все элементы окажутся в одной корзине. Поэтому саму сущность ключом Map лучше не делать: ключом служит id, Map<Long, Order>.
Где спотыкаются
Вешают на entity Lombok @Data или @EqualsAndHashCode без настройки. Это ровно «все поля», вместе с ленивыми коллекциями и циклами.
Предлагают сделать поля final. Спецификация JPA запрещает final у персистентных полей, так что неизменяемой сущность этим не сделать.
Считают, что хватит одного id в hashCode, и забывают, что до сохранения он null, а после меняется.