Как должны быть реализованы equals и hashcode при использовании JPA и Hibernate



Как должны быть реализованы равенства и хэш-код класса модели в Hibernate? Каковы типичные ошибки? Является ли реализация по умолчанию достаточно хорошей для большинства случаев? Есть ли смысл использовать бизнес-ключи?



Мне кажется, что довольно сложно заставить его работать в любой ситуации, когда учитываются ленивая выборка, генерация идентификаторов, прокси и т. д.

821   8  

8 ответов:

Hibernate имеет хорошее и длинное описание того, когда / как переопределить equals()/hashCode() in документация

суть в том, что вам нужно только беспокоиться об этом, если ваша сущность будет частью Set или если вы собираетесь отсоединять / присоединять его экземпляры. Последнее не так часто встречается. Первый обычно лучше всего обрабатывается через:

  1. основывая equals()/hashCode() на бизнес-ключ-например, уникальная комбинация атрибутов, которые не собирается меняться во время жизни объекта (или, по крайней мере, сеанса).
  2. если это невозможно, база equals()/hashCode() на первичном ключе, если он установлен и идентификатор объекта / System.identityHashCode() в противном случае. Элемент важно часть здесь заключается в том, что вам нужно перезагрузка Ваш набор после добавления новой сущности и сохранения; в противном случае вы можете столкнуться со странным поведением (в конечном итоге приводящим к ошибкам и / или повреждению данных), потому что ваша сущность может быть выделено ведро, не соответствующее его текущему hashCode().

Я не думаю, что принятый ответ точный.

чтобы ответить на исходный вопрос:

достаточно ли хороша реализация по умолчанию для большинства случаев?

Да, в большинстве случаев, это.

вам нужно только переопределить equals() и hashcode() если сущность будет использоваться в Set (что очень часто)и объект будет отсоединен от спящего режима, а затем повторно присоединен к нему сеансы (что является необычным использованием hibernate).

принятый ответ показывает, что методы должны быть переопределены, если или условие истинно.

когда сущность загружается через ленивую загрузку, это не экземпляр базового типа, а динамически генерируемый подтип, генерируемый javassist, поэтому проверка того же типа класса завершится неудачей, поэтому не используйте:

if (getClass() != that.getClass()) return false;

вместо этого использовать:

if (!(otherObject instanceof Unit)) return false;

что также является хорошей практикой, как объяснено на реализация равных в практике Java.

по той же причине, доступ непосредственно к полям, может не работать и возвращать null, а не базовое значение, поэтому не используйте сравнение свойств, а используйте геттеры, поскольку они могут инициировать загрузку базовых значений.

лучшие equals/hashCode реализация-это когда вы используете уникальный бизнес-ключ.

бизнес-ключ должен быть последовательным по всем сущность состояния (переходный, прикрепленный, отсоединенный, удаленный), поэтому вы не можете полагаться на id для равенства.

другой вариант-переключиться на использование идентификаторы UUID, назначается логикой приложения. Таким образом, вы можете использовать UUID для equals/hashCode потому что идентификатор присваивается до того, как объект будет сброшен.

вы даже можете использовать идентификатор объекта для equals и hashCode, но это требует, чтобы вы всегда возвращали одно и то же hashCode значение, чтобы убедиться, что значение хэш-кода сущности согласовано во всех переходах состояния сущности. Проверьте этот пост для получения дополнительной информации по этой теме.

Да, это тяжело. В моем проекте equals и hashCode оба полагаются на идентификатор объекта. Проблема этого решения заключается в том, что ни один из них не работает, если объект еще не был сохранен, как идентификатор по базе. В моем случае это терпимо, так как почти во всех случаях объекты сохраняются сразу. Кроме этого, он отлично работает и прост в реализации.

если вам случилось переопределить equals, убедитесь, что вы выполняете свои контракты: -

  • симметричность
  • светоотражающая
  • транзитивный
  • последовательный
  • NON NULL

и заменить hashCode, так как его контракт полагаются на equals реализация.

Джошуа блох (дизайнер Collection framework) настоятельно призвал соблюдать эти правила.

  • пункт 9: всегда переопределить хэш-код при переопределении равен

есть серьезный непреднамеренный эффект, когда вы не следуете этим контрактам. Например List.contains(Object o) может возвращать неправильный boolean значение как не выполненный генеральный контракт.

в документации Hibernate 5.2 говорится, что вы, возможно, не захотите реализовывать хэш - код и равны вообще-в зависимости от вашей ситуации.

https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode

Как правило, два объекта, загруженные из одного сеанса, будут равны, если они равны в базе данных (без реализации хэш-кода и 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 
}

}

Comments

    Ничего не найдено.