Как должны быть реализованы equals и hashcode при использовании JPA и Hibernate
Вопрос
Как должны быть реализованы значения класса модели equals и hashcode в Hibernate?Каковы распространенные подводные камни?Достаточно ли хороша реализация по умолчанию для большинства случаев?Есть ли какой-то смысл использовать бизнес-ключи?
Мне кажется, что довольно сложно заставить его работать правильно в любой ситуации, когда учитываются отложенная выборка, генерация идентификаторов, прокси и т.д.
Решение
Hibernate имеет хорошее и длинное описание того, когда / как переопределить equals ()
/ hashCode ()
в документация
Суть в том, что вам нужно беспокоиться об этом, только если ваша сущность будет частью Set
или если вы собираетесь отсоединять / прикреплять ее экземпляры. Последнее не так часто. С первым обычно лучше всего обращаться через:
equals ()
/ hashCode ()
для бизнес-ключа - например, уникальная комбинация атрибутов, которая не будет изменяться в течение времени жизни объекта (или, по крайней мере, сеанса). equals ()
/ hashCode ()
для первичного ключа, если оно установлено, и идентификатор объекта / System.identityHashCode ()
иначе. важный раздел здесь заключается в том, что вам нужно перезагрузить свой Сет после добавления в него новой сущности и ее сохранения; в противном случае вы можете столкнуться со странным поведением (в конечном итоге приводящим к ошибкам и / или повреждению данных), поскольку ваша сущность может быть выделена в корзину, не соответствующую ее текущему hashCode ()
. Другие советы
Я не думаю, что принятый ответ является точным.
Чтобы ответить на оригинальный вопрос:
Достаточно ли хороша реализация по умолчанию для большинства случаев?
Ответ - да, в большинстве случаев это так.
Вам нужно только переопределить equals ()
и hashcode ()
, если объект будет использоваться в Set
(что очень распространено) ) И объект будет отсоединен и впоследствии повторно присоединен к сеансам гибернации (что является редким использованием гибернации).
Принятый ответ указывает, что методы должны быть переопределены, если условие any выполнено.
Когда объект загружается с помощью отложенной загрузки, это не экземпляр базового типа, а динамически генерируемый подтип, сгенерированный javassist, поэтому проверка на тот же тип класса завершится неудачей, поэтому не используйте: р>
if (getClass() != that.getClass()) return false;
вместо этого используйте:
if (!(otherObject instanceof Unit)) return false;
, что также является хорошей практикой, как объясняется на Внедрении равных в практиках Java а>. р>
по той же причине доступ к полям может не работать и возвращать нулевое значение вместо базового значения, поэтому не используйте сравнение со свойствами, а используйте методы получения, поскольку они могут инициировать загрузку базовых значений. р>
Лучшая реализация equals
/ hashCode
- это когда вы используете уникальный бизнес-ключ . Р>
Бизнес-ключ должен быть одинаковым во всех состоянии объекта переходы (временные, прикрепленные, отсоединенные, удаленные), поэтому нельзя полагаться на равенство id.
Другой вариант - перейти на использование идентификаторов UUID , назначенных приложением логика. Таким образом, вы можете использовать UUID для equals
/ hashCode
, поскольку идентификатор назначается до сброса сущности.
Вы даже можете использовать идентификатор объекта для equals
и hashCode
, но для этого необходимо всегда возвращать одно и то же значение hashCode
, чтобы вы убедитесь, что значение hashCode объекта согласовано во всех переходах состояния объекта. Зацените этот пост, чтобы получить дополнительную информацию по этой теме. . р>
Да, это сложно. В моем проекте equals и hashCode оба полагаются на id объекта. Проблема этого решения состоит в том, что ни один из них не работает, если объект еще не был сохранен, поскольку идентификатор генерируется базой данных. В моем случае это терпимо, так как почти во всех случаях объекты сохраняются сразу. Кроме того, он прекрасно работает и его легко реализовать.
Если вам случилось переопределить equals
, убедитесь, что вы выполняете его контракты:-
- СИММЕТРИЯ
- РЕФЛЕКСИВНЫЙ
- ТРАНЗИТИВНЫЙ
- ПОСЛЕДОВАТЕЛЬНЫЙ
- НЕНУЛЕВОЙ
И переопределить hashCode
, поскольку его контракт основан на equals
реализация.
Джошуа Блох (дизайнер Collection framework) настоятельно призвал соблюдать эти правила.
- пункт 9 повестки дня:Всегда переопределяйте хэш-код, когда вы переопределяете equals
Несоблюдение этих контрактов может привести к серьезным непреднамеренным последствиям.Например List.contains(Object o)
может вернуться неправильно boolean
значение, поскольку генеральный контракт не выполнен.
В документации по Hibernate 5.2 говорится, что вы можете не захотеть реализовывать hashCode и вообще равно - в зависимости от вашей ситуации.
Как правило, два объекта, загруженные из одного сеанса, будут равны, если они равны в базе данных (без реализации hashCode и equals).
Это становится сложным, если вы используете два или более сеансов. В этом случае равенство двух объектов зависит от реализации метода equals.
Кроме того, у вас возникнут проблемы, если ваш метод equals сравнивает идентификаторы, которые генерируются только при сохранении объекта в первый раз. Они могут еще не быть там, когда вызывается equals.
Здесь есть очень хорошая статья: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html
Цитирую важную строку из статьи:
Мы рекомендуем реализовать equals () и hashCode () с использованием бизнес-ключа. равенство. Бизнес-равенство означает, что метод equals () сравниваются только те свойства, которые образуют бизнес-ключ, ключ, который определил бы наш экземпляр в реальном мире (естественный кандидат ключ):
Проще говоря
public class Cat {
...
public boolean equals(Object other) {
//Basic test / class cast
return this.catId==other.catId;
}
public int hashCode() {
int result;
return 3*this.catId; //any primenumber
}
}