Использование автоматически сгенерированного идентификатора объекта hibenate в методах equals и hashcode
Прекрасные равные и хэш-код, вся теория здесь, а также здесь
Я принял решение использовать автоматически сгенерированный идентификатор в пределах equals() и hashcode() в ряде моих спящих объектов сущности / домена.
Тем не менее, некоторые веб-сайты говорят, что вы никогда не должны этого делать из-за риска сохранения объекта в базе данных в первый раз, пока он находится в процессе сравнения или использования хэш-кода.
Моя точка зрения заключается в том, что в большинстве случаев это гораздо менее вероятно, чем изменение любого другого поля.
Индивидуальные доменные объекты имеют идентификатор, сгенерированный один раз, когда они впервые создаются, тогда как почти каждое другое поле имеет возможность быть измененным во время обычных бизнес-процессов (даже уникальное имя пользователя может быть изменено...).
И во многих моих доменных объектах уникальный идентификатор - это почти единственное подходящее поле, которое нужно рассмотреть (Персона, Адрес, Домашнее животное,... Клиент и т. Д. И т. Д. Объединение полей - это хорошая идея, но никогда не использовать автоматически сгенерированный идентификатор, Я думаю, не очень хороший совет.
Я что-то упускаю?
3 ответа
Вы должны прочитать Equals и HashCode на Wiki сообщества Hibernate.
Основная причина не использовать идентификатор базы данных в equals
и, косвенно, hashCode
предназначен для работы с сохраненными, но не постоянными сущностями. До настойчивости все ваши экземпляры будут equal
, если только вы не позаботитесь об этом случае явно.
Если вы знаете, что не будете участвовать в этом сценарии и убедитесь, что он хорошо задокументирован, у вас вполне может быть все в порядке. Вы всегда можете изменить реализации позже.
Нет, я не думаю, что вы упускаете что-то фундаментальное. "Основная причина" проблемы с использованием идентификатора базы данных для equals и hashCode заключается в том, что сгенерированный Hibernate ID хранится в изменяемом поле, и значение этого поля изменяется, когда Hibernate назначает идентификатор. Равные и hashCode должны основываться на неизменяемом состоянии, как описано в статье "Подводный камень #3" в статье Одерского / Ложки / Веннерса о написании методов equals / hashCode. Это означает, среди прочего, что вы не можете добавить экземпляр в Set или сравнить его с другим экземпляром до тех пор, пока он не будет сохранен, когда hashCode станет фиксированным.
Единственное, что вам может не хватать, это то, как сложно отслеживать, когда назначается идентификатор, поскольку это делается автоматически в Hibernate. Конечно, вы никогда не могли бы позвонить setId(someNewId)
, но вы могли бы выполнить запрос, который запускает сброс сеанса, и целая группа временных объектов, не связанных с запросом, внезапно меняет свои идентификаторы с нуля на не-ноль.
Сообщество Hibernate часто рекомендует использовать бизнес-ключ для equals и hashCode, но этот подход также страдает от той же проблемы изменяемого до инициализации. Если ваша сущность является членом постоянного набора, который активно загружается Hibernate, то его можно добавить в набор до инициализации его полей, поэтому hashCode, основанный на этих полях, также не работает. В этом отчете об ошибке в Hibernate обсуждается проблема с равенством бизнес-ключей.
Джеймс Брандеге рекомендует назначить идентификатор в коде приложения, чтобы избежать этой проблемы. У Ланса Арлауса есть аналогичный подход, использующий фабрику объектов, которая назначает идентификаторы.
Если вы действительно хотите использовать автоматически сгенерированный идентификатор для equals и hashCode, рассмотрите возможность использования Assert.notNull(id)
в ваших реализациях equals и hashCode для обнаружения ошибок кодирования.
Смотрите также еще один вопрос, посвященный стековому потоку, с множеством обсуждений различных подходов.
Я столкнулся с проблемой равенства при автоматической генерации идентификаторов из-за того, что я поместил мои объекты в Set, как только они были созданы на основе нового пользовательского ввода. Таким образом, я не сравнивал их явно, я просто положил их в набор. В тот момент это уже было неудачно, потому что Set полагается на equals()/hashCode() (которые снова были основаны на идентификаторе объекта, потому что у меня не было никакой другой опции).
Затем я изменил его на собственно генерацию и присвоение идентификаторов самостоятельно, что было гораздо лучшим решением.
Пожалуйста, прочитайте эту статью, она точно объясняет проблему: dont-let-hibernate-steal-your-identity