Как правильно реализовать equals и hashCode для сущностей с учетом id и класса
Что проверяют
Речь почти всегда о JPA-сущностях, и проверяют, знаете ли вы, чем они отличаются от обычных объектов: id появляется только после сохранения, а вместо объекта может прийти прокси. Следом спрашивают, что будет с сущностью в HashSet до и после persist.
Как отвечать
Сначала контракт: равные объекты обязаны иметь равный hashCode, а сам hashCode не должен меняться, пока объект лежит в хеш-коллекции. У сущности с генерируемым id второе условие ломается: до persist id равен null, после появляется значение.
Рабочая схема для сущности с суррогатным ключом:
equals:this == oсразуtrue; дальше сравнение реальных классов с учётом прокси, например черезHibernate.getClass(o); потомgetId() != null && getId().equals(other.getId()). Две новые сущности без id равны только самим себе;hashCode: константа для класса, обычноgetClass().hashCode(). Он не меняется при присвоении id, а цена понятна: все сущности класса в одной корзине, что приемлемо для коллекций связей на десятки элементов;- доступ через геттеры: у прокси поля не заполнены,
other.idвернётnull, аother.getId()отдаст id без инициализации.
Альтернативы тоже стоит назвать. Натуральный ключ через @NaturalId (email, ИНН, артикул), если он неизменяем. Либо UUID, который присваивается в конструкторе ещё до сохранения: тогда equals и hashCode по нему стабильны, и обычный подход работает без оговорок.
Где спотыкаются
Считают hashCode от id, как в шаблоне из IDE. Новую сущность кладут в Set, сохраняют, id появляется, хеш меняется, и contains() больше её не находит, а remove() не удаляет.
Сравнивают классы через getClass() != o.getClass() без учёта прокси. Лениво загруженная связь приходит как User$HibernateProxy$..., и объект оказывается не равен той же записи, загруженной другим путём.
Вешают на сущность @Data или @EqualsAndHashCode из Lombok. В сравнение попадают все поля, включая ленивые коллекции: получаете LazyInitializationException вне сессии или StackOverflowError на двунаправленной связи.