Как следует использовать равенства и хэш-код при использовании JPA и Hibernate - PullRequest
88 голосов
/ 28 октября 2009

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

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

Ответы [ 8 ]

63 голосов
/ 28 октября 2009

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

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

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

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

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

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

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

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

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

12 голосов
/ 10 января 2013

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

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

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

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

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

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

10 голосов
/ 28 июня 2014

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

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

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

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

6 голосов
/ 28 октября 2009

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

2 голосов
/ 02 марта 2016

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

  • СИММЕТРИЯ
  • ОТРАЖАТЕЛЬ
  • ПЕРЕХОДНЫЙ
  • CONSISTENT
  • NON NULL

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

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

  • позиция 9: Всегда переопределять hashCode, когда вы переопределяете равным

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

1 голос
/ 20 октября 2016

В документации по 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 сравнивает идентификаторы, которые генерируются только при сохранении объекта в первый раз. Они могут еще не быть там, когда вызывается equals.

0 голосов
/ 02 января 2016

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

}
...