Hibernate: когда необходимо реализовать equals () и hashCode (), и если да, то как? - PullRequest
7 голосов
/ 01 февраля 2012

Основываясь на различном плохом опыте мое правило как программиста на Java - реализовывать equals() и hashCode() только для неизменных объектов, где два экземпляра объекта действительно взаимозаменяемы.

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

  1. Получить вещь с определенной идентификацией.
  2. Изменить ее.
  3. Добавить его в набор.
  4. (позже) Получить еще одну вещь с таким же именем.
  5. Изменить ее.
  6. Добавить ее к тому женабор.
  7. Не замечать, что это добавление на самом деле не происходит, так как набор думает, что вещь уже существует.
  8. Сделайте что-нибудь с вещами в наборе.
  9. Не замечать, что изменение с шага (5) игнорируется, и у нас все еще есть состояние с шага (2).

И в целом в течение моей карьеры Java I не нашел широкого применения для equals() , кроме для (1) объектов значения иг (2) положить вещи в коллекции.Я также обнаружил, что конструкторы / конструкторы с неизменяемостью + копированием и модификацией, как правило, гораздо счастливее, чем сеттеры.Два объекта могут иметь один и тот же идентификатор и могут представлять один и тот же логический объект, но если они имеют разные данные - если они представляют моментальные снимки концептуального объекта в разное время - тогда они не equal().

* 1037.* В любом случае, я сейчас в магазине Hibernate, и мои более опытные коллеги по Hibernate говорят мне, что этот подход не сработает.В частности, утверждается, что в следующем сценарии -
  1. Hibernate загружает вещь из базы данных - мы назовем это экземпляром h1.
  2. Эта вещьмаршалируется и отправляется куда-то через веб-сервис.
  3. Клиент веб-сервиса возится с ним и отправляет измененную версию обратно.
  4. Модифицированная версия демонтируется на сервере - мы позвонимэто экземпляр h4.
  5. Мы хотим, чтобы Hibernate обновил базу данных с изменениями.

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

Итак, что я хочу знать:

  • Это правда?
  • Если так, то почему?Для чего Hibernate использует equals() для?
  • Если Hibernate требует, чтобы h1 и h4 были равны, как он (и как мы) отслеживаем, какая из них является модифицированной версией?

Примечание: Я прочитал Реализация equals () и hashCode () в документах Hibernate, и это не касается ситуации, которую яЯ обеспокоен, по крайней мере, напрямую, и не объясняю в деталях, что Hibernate действительно нужно из equals() и hashCode().И ответ на не совпадает с хеш-кодом в Hibernate , иначе я бы не стал его публиковать.

Ответы [ 2 ]

4 голосов
/ 07 мая 2013

Прежде всего, ваша первоначальная идея, что вы должны реализовывать equals () и hashCode () только для неизменяемых объектов, безусловно, работает, но это строже, чем нужно.Вам просто нужны эти два метода, чтобы полагаться на неизменные поля.Любое поле, значение которого может измениться, не подходит для использования в этих двух методах, но другие поля не должны быть неизменными.

Сказав это, Hibernate знает, что это один и тот же объект, сравнивая их первичные ключи.Это приводит к тому, что многие пишут эти два метода, полагаясь на первичный ключ.Документы Hibernate рекомендуют не делать этого, но многие люди игнорируют этот совет без особых проблем.Это означает, что вы не можете добавлять сущности в набор до тех пор, пока они не будут сохранены, что является ограничением, с которым не так уж сложно жить.

В документах Hibernate рекомендуется использовать бизнес-ключ.Но бизнес-ключ должен опираться на поля, которые однозначно идентифицируют объект.В документах Hibernate говорится «используйте бизнес-ключ, который представляет собой комбинацию уникальных, обычно неизменяемых атрибутов».Я использую поля, которые имеют уникальные ограничения в базе данных.Итак, если ваш оператор Sql CREATE TABLE задает ограничение как

CONSTRAINT uc_order_num_item UNIQUE (order_num, order_item)

, то эти два поля могут быть вашим бизнес-ключом.Таким образом, если вы измените один из них, и Hibernate, и Java будут рассматривать измененный объект как другой объект.Конечно, если вы измените одно из этих «неизменяемых» полей, вы испортите любой набор, к которому они принадлежат.Поэтому я думаю, вам нужно четко задокументировать, какие поля составляют бизнес-ключ, и написать свое приложение, понимая, что поля в бизнес-ключе никогда не должны изменяться для постоянных объектов.Я понимаю, почему люди игнорируют совет и используют первичный ключ.Но вы можете определить первичный ключ следующим образом:

CONSTRAINT pk_order_num_item PRIMARY KEY (order_num, order_item)

И у вас все еще будет та же проблема.

Лично я хотел бы видеть аннотацию, которая определяет каждое поле в бизнесеключ, и есть проверка IDE, которая проверяет, изменил ли я его для постоянных объектов.Может быть, это требует слишком много.

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

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

1 голос
/ 01 февраля 2012

Какую семантику навязывает JPA / Hibernate?

В спецификации JPA говорится следующее.

2.4 Первичные ключи и идентификация сущности

Каждый объект должен иметь первичный ключ.... Значение его первичного ключа однозначно идентифицирует экземпляр сущности в контексте постоянства и для EntityManager операций

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

Но совет Hibernate *1020*, на который вы ссылаетесь (и другую статью, которую я видел), говорит не делатьэто, а скорее использовать «бизнес-ключ», а не первичный ключ.Причина этого, по-видимому, заключается в том, что мы не можем гарантировать, что объектный объект будет иметь значение для сгенерированного первичного ключа до тех пор, пока объект не будет синхронизирован (используя EntityManager.flush()) с базой данных.

...