Як слід застосовувати рівний і хеш-код при використанні JPA та Hibernate


103

Як повинні бути реалізовані рівні моделі та хеш-код класу моделі в режимі глибокого сну? Які загальні підводні камені? Чи реалізація за замовчуванням достатня для більшості випадків? Чи є сенс використовувати ділові ключі?

Мені здається, що досить важко змусити його правильно працювати в будь-якій ситуації, коли враховується лінивий добір, генерація ідентифікаторів, проксі тощо.


Дивіться також stackoverflow.com/a/39827962/548473 (реалізація пружинних-даних JPA)
Григорій Кислін

Відповіді:


74

У сплячому режимі є хороший і довгий опис того, коли / як перевизначити equals()/ hashCode()в документації

Суть її полягає в тому, що вам потрібно потурбуватися лише про те, якщо ваша організація буде частиною Setабо якщо ви збираєтеся від'єднувати / приєднувати її екземпляри. Останнє не так часто. З першим зазвичай найкраще обробляти:

  1. Базування equals()/ hashCode()на діловому ключі - наприклад, унікальна комбінація атрибутів, яка не збирається змінюватися протягом життя об’єкта (або, принаймні, сесії).
  2. Якщо вищезазначене неможливо, базуйте equals()/ hashCode()на первинному ключі, якщо його встановлено та ідентифікатор об'єкта / System.identityHashCode()інакше. Важливою частиною тут є те , що вам потрібно перезавантажити ваш набір після нового об'єкта було додано до нього і зберігається; інакше у вас може виникнути дивна поведінка (врешті-решт, це призведе до помилок та / або пошкодження даних), оскільки ваша організація може бути призначена для відра, що не відповідає його поточному hashCode().

1
Коли ви говорите "перезавантажити" @ ChssPly76, ви маєте на увазі робити refresh()? Як ваша організація, яка підкоряється Setдоговору, опиняється в неправильному відрізці (якщо припустити, що у вас є достатньо хороша реалізація хеш-коду).
не послідовник

4
Оновіть колекцію або перезавантажте весь (власник) об'єкт, так. Що стосується неправильного відра: а) ви додаєте нову сутність для встановлення, її ідентифікатор ще не встановлений, тому ви використовуєте identHashCode, який розміщує вашу сутність у відрізці 1. б) ваша сутність (у межах набору) зберігається, тепер вона має ідентифікатор, і тому ви використовуєте hashCode () на основі цього ідентифікатора. Вона відрізняється від вище , і був би розмістила свій об'єкт в відрі # 2. Тепер, якщо припустити, що ви маєте посилання на цю сутність в іншому місці, спробуйте зателефонувати, Set.contains(entity)і ви повернетесь false. Те ж саме стосується get () / put () / тощо ...
ChssPly76

Має сенс, але ніколи не використовував ідентифікаторHashCode сам, хоча я бачу, що він використовується у джерелі сплячки, як у їх ResultTransformers
не послідовник

1
Під час використання Hibernate ви також можете зіткнутися з цією проблемою , до якої я досі не знайшов рішення.
Джованні Ботта

@ ChssPly76 Через бізнес-правила, які визначають, чи два об'єкти рівні, мені потрібно буде базувати свої методи рівних / хеш-кодів на властивостях, які можуть змінюватися протягом життя об’єкта. Це справді велика справа? Якщо так, як мені це обійти?
ubiquibacon

39

Я не вважаю, що прийнята відповідь є точною.

Щоб відповісти на початкове запитання:

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

Відповідь - так, у більшості випадків це так.

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

Прийнята відповідь вказує на те, що методи повинні бути скасовані, якщо будь-яка умова істинна.


Це узгоджується з моїм спостереженням, час з’ясувати, чому .
Властиміл Овчачик

"Вам потрібно лише замінити рівняння () і хеш-код (), якщо сутність буде використовуватися в наборі", цілком достатньо, якщо деякі поля ідентифікують об'єкт, і тому ви не хочете посилатися на Object.equals () для ідентифікації об’єкти.
davidxxx

17

Найкраще equals/ hashCodeреалізація при використанні унікального бізнес - ключа .

Діловий ключ повинен бути узгодженим для всіх переходів стану сутності (тимчасових, прикріплених, від'єднаних, видалених), тому ви не можете розраховувати на id для рівності.

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

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


+1 для уїдського підходу. Покладіть це на думку BaseEntityі ніколи більше не думайте про цю проблему. Це займає трохи місця на стороні db, але за цією ціною ви краще заплатите за комфорт :)
Martin Frey

12

Коли суб'єкт завантажується за допомогою ледачого завантаження, це не екземпляр базового типу, а динамічно генерований підтип, що генерується javassist, тому перевірка того ж типу класу не вдасться, тому не використовуйте:

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

замість цього використовуйте:

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

що також є хорошою практикою, як це пояснено у розділі Впровадження рівних у практиці Java .

з тієї ж причини, звернення безпосередньо до полів може не працювати і повертати нулеве замість базового значення, тому не використовуйте порівняння властивостей, а використовуйте getters, оскільки вони можуть спричинити завантаження базових значень.


1
Це працює, якщо ви порівнюєте об’єкти конкретних класів, які не спрацювали в моїй ситуації. Я порівнював об’єкти суперкласів, і в цьому випадку для мене працював цей код: obj1.getClass (). IsInstance (obj2)
Tad

6

Так, важко. У моєму проекті дорівнює і hashCode обидва покладаються на ідентифікатор об'єкта. Проблема цього рішення полягає в тому, що жоден з них не працює, якщо об'єкт ще не зберігався, оскільки ідентифікатор створюється базою даних. У моєму випадку це допустимо, оскільки майже у всіх випадках об'єкти зберігаються відразу. Крім цього, він чудово працює і його легко здійснити.


Я думаю, що ми зробили це використовувати ідентичність об'єкта у випадку, коли ідентифікатор не був сформований
Кеті Ван Стоун,

2
Проблема тут полягає в тому, що якщо ви зберігаєте об'єкт, ваш хеш-код змінюється. Це може мати великі згубні результати, якщо об'єкт вже є частиною структури даних на основі хешу. Отже, якщо ви завершите використання ідентичності об'єкта, краще продовжуйте використовувати id obj, поки об'єкт не буде повністю звільнений (або видаліть об’єкт із будь-яких хеш-структур, збережіться, а потім додайте його знову). Особисто я вважаю, що було б найкраще не використовувати id, а базувати хеш на незмінних властивостях об'єкта.
День Кевіна

1

У документації Hibernate 5.2 написано, що ви, можливо, не хочете реалізовувати hashCode і дорівнює рівню - залежно від вашої ситуації.

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

Як правило, два об'єкти, завантажені з одного сеансу, будуть рівними, якщо вони рівні в базі даних (без реалізації hashCode та рівних).

Це ускладнюється, якщо ви використовуєте два або більше сеансів. У цьому випадку рівність двох об'єктів залежить від вашої реалізації методу рівних.

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


0

Тут є дуже приємна стаття: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

Цитуючи важливий рядок із статті:

Ми рекомендуємо реалізувати рівні () та хеш-код () з використанням рівності бізнес-ключів. Рівність бізнес-ключа означає, що метод 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 
}

}

0

Якщо у вас трапилось перекриття equals, переконайтеся, що ви виконуєте його договори: -

  • СИМЕТРІЯ
  • РЕФЛЕКТИВНИЙ
  • ПЕРЕХОДНИЙ
  • КОНСИСТЕНТ
  • NON NULL

І переосмислити hashCode, оскільки його контракт покладається на equalsвиконання.

Джошуа Блох (дизайнер рамки колекції) наполегливо закликав дотримуватися цих правил.

  • пункт 9: Завжди переосмислюйте хеш-код, коли ви переосмислюєте рівняння

Якщо ви не дотримуєтесь цих контрактів, є серйозні випадкові дії. Наприклад, List#contains(Object o)може повернути неправильну booleanвартість, оскільки загальний договір не виконується.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.