Как следует использовать равенства и хэш-код при использовании JPA и Hibernate

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

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

Ответ 1

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

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

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

что также является хорошей практикой, как описано в Внедрение равных в Java Practices.

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

Ответ 4

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

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

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

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

Ответ 5

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

Ответ 6

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

  • СИММЕТРИЯ
  • REFLECTIVE
  • TRANSITIVE
  • CONSISTENT
  • NON NULL

И переопределить hashCode, поскольку его контракт зависит от реализации equals.

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

  • item 9: Всегда переопределять hashCode, когда вы переопределяете equals

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

Ответ 7

Здесь очень хорошая статья: 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 
}

}

Ответ 8

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

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

Как правило, два объекта, загруженные из одного сеанса, будут равны, если они равны в базе данных (без реализации hashCode и равно).

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

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