Должно ли поле id объекта JPA рассматриваться равным и hashCode?
Я столкнулся с проблемой при написании тестов для приложения базы данных с использованием JPA2 и EclipseLink:
Я добавляю какой-то объект в базу данных, извлекаю его позже и хочу сравнить его с экземпляром, который имеет значения, которые я ожидаю, чтобы подтвердить, что сложение работает так, как я предполагал.
Сначала я написал что-то вроде
assertEquals(expResult, dbResult);
который не удалось, потому что я не могу знать значение поля id
, которое генерируется базой данных, и поэтому dbResult
отличается от expResult
, который я создал с помощью new
, и заполнен вручную.
Я вижу два варианта:
-
Либо я удаляю поле id
из equals
и hashCode
, чтобы сравнение было основано только на "реальных значениях". Я не знаю, вызвало ли это проблемы в базе данных или в других местах.
-
Или я пишу свои тесты, чтобы явно проверять каждое поле, кроме id
вручную.
Что мне делать?
Ответы
Ответ 1
Вы можете найти много споров об этом. Моя позиция заключается в том, что вы абсолютно не используете первичный ключ базы данных для чего-либо в своем приложении. Он должен быть полностью невидим. Определите свои объекты в приложении другим свойством или комбинацией свойств.
На фронте операций тестирования настойчивости вам действительно нужно проверить, правильно ли были сохранены и загружены поля, и, возможно, первичный ключ получил определенное значение при его сохранении. Это, вероятно, совсем не работает для метода equals.
Ответ 2
Из книги Hibernate in Action рекомендуется использовать бизнес-ключ и проверить на нем равенство. Бизнес-ключ - это свойство или некоторая комбинация свойств, которая уникальна для каждого экземпляра с одинаковым идентификатором базы данных ". В других областях говорится, что он не использует идентификатор как одно из этих свойств и не использует значения в коллекциях.
Ответ 3
Опираясь на созданные в базе данных идентификаторы в вашей реализации equals
и hashCode
не рекомендуется. Вы должны полагаться на поистине уникальные/полу-уникальные атрибуты ваших классов при проверке равенства и генерации значений хэш-кода. Документация Hibernate содержит обширную страницу, на которой это обсуждается, и факты в ней применимы к более или менее каждому поставщику JPA.
Основная причина использования бизнес-ключей по генерируемым базам значениям в вашей реализации equals
и hashCode
заключается в том, что поставщик JPA должен фактически выпустить SELECT
после сохранения объекта в базе данных. Если вы сравниваете объекты с использованием идентификаторов, сгенерированных с помощью базы данных, тогда вы получите тест равенства, который не выполняется в следующих сценариях:
- Если
E1
и E2
являются объектами класса E (которые проверяют равенство с использованием генерируемых идентификаторами базы данных), то если E1
и E2
будут равны, если они еще не были сохранены в базе данных. Это не то, что вы хотите, особенно если хотите сохранить E1
и E2
в некотором Set
перед сохранением. Это хуже, если атрибуты E1
и E2
имеют разные значения; реализация equals
предотвратила бы добавление двух существенно разных объектов в Set
, а реализация hashCode
даст вам время поиска O(n)
, когда сущности просматриваются с помощью HashMap
с использованием первичного ключа.
- Если
E1
является управляемым объектом, который был сохранен, а E2
является сущностью, которая не была сохранена, тогда тест равенства будет считать, что E1
!= E2
в сценарии, где все значения атрибутов E1
и E2
(кроме идентификаторов) аналогичны. Опять же, это, вероятно, не то, что вы хотите, особенно если вы хотите избежать дублирования сущностей в базе данных, которые отличаются только идентификаторами, создаваемыми в базе данных.
Таким образом, реализации equals
и hashCode
должны использовать бизнес-ключи, чтобы демонстрировать согласованное поведение как для сохраняемых, так и для непервизированных объектов.
Ответ 4
Я бы написал свой тест, чтобы явно проверить поля. Чтобы сделать это проще, перед выполнением теста assertEqual я установил идентификатор как ожидаемого, так и фактического результата в одно и то же предопределенное значение, а затем применил метод normal equals.
Удаление идентификатора из равных не оправдано, просто потому, что тестирование немного затруднено. Вы отказываетесь от серьезных преимуществ, а также целостности кода.