У меня проблема с hashCode ()
, который делегирует неинициализированным объектам с помощью спящего режима .
Моя модель данных выглядит следующим образом (следующий код сильно сокращен, чтобы подчеркнуть проблему и поэтому сломан, не копируйте!):
class Compound {
@FetchType.EAGER
Set parts = new HashSet();
String someUniqueName;
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getSomeUniqueName() == null) ? 0 : getSomeUniqueName().hashCode());
return result;
}
}
class Part {
Compound compound;
String someUniqueName;
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getCompound() == null) ? 0 : getCompound().hashCode());
result = prime * result + ((getSomeUniqueName() == null) ? 0 : getSomeUniqueName().hashCode());
return result;
}
}
Обратите внимание, что реализация hashCode ()
тщательно следует совету, приведенному в документации по спящему режиму .
Теперь, если я загружаю объект типа Compound
, он охотно загружает HasSet
с частями . Это вызывает hashCode ()
для частей, который, в свою очередь, вызывает hashCode ()
для соединения. Однако проблема в том, что на данном этапе не все значения, которые рассматриваются для создания хэш-кода соединения пока нет.Следовательно, хэш-код частей изменяется после завершения инициализации, тем самым нарушая контракт HashSet
и приводя ко всем видам трудноотслеживаемых ошибок (например, наличие один и тот же объект в деталях установлен дважды).
Итак, мой вопрос: Какое простейшее решение избежать этой проблемы (я бы не хотел писать классы для пользовательской загрузки / инициализации)? Я что-то здесь делаю совсем не так?
Редактировать : Я что-то здесь упустил? Кажется, это основная проблема, почему я нигде не нахожу об этом ничего?
Вместо использования идентификатора базы данных для сравнения на равенство, вы должны использовать набор свойств для equals (), которые идентифицируют ваш отдельные объекты. [...] Нет необходимости использовать постоянный идентификатор, поэтому называется "бизнес-ключом" гораздо лучше. Это естественный ключ, но это раз нет ничего плохого в его использовании! ( статья из hibernate )
И
Рекомендуется реализовать equals () и hashCode (), используя Деловое равенство ключей. Равенство бизнес-ключей означает, что равенство () сравнивает только свойства, составляющие бизнес-ключ. это ключ, который идентифицировал бы наш экземпляр в реальном мире (естественный кандидат ключ). ( документация по спящему режиму )
Правка: Это трассировка стека при загрузке (в случае, если это помогает). В этот момент атрибут someUniqueName
имеет значение NULL, и поэтому хэш-код вычисляется неправильно.
Compound.getSomeUniqueName() line: 263
Compound.hashCode() line: 286
Part.hashCode() line: 123
HashMap.put(K, V) line: 372
HashSet.add(E) line: 200
HashSet(AbstractCollection).addAll(Collection extends E>) line: 305
PersistentSet.endRead() line: 352
CollectionLoadContext.endLoadingCollection(LoadingCollectionEntry, CollectionPersister) line: 261
CollectionLoadContext.endLoadingCollections(CollectionPersister, List) line: 246
CollectionLoadContext.endLoadingCollections(CollectionPersister) line: 219
EntityLoader(Loader).endCollectionLoad(Object, SessionImplementor, CollectionPersister) line: 1005
EntityLoader(Loader).initializeEntitiesAndCollections(List, Object, SessionImplementor, boolean) line: 993
EntityLoader(Loader).doQuery(SessionImplementor, QueryParameters, boolean) line: 857
EntityLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean) line: 274
EntityLoader(Loader).loadEntity(SessionImplementor, Object, Type, Object, String, Serializable, EntityPersister, LockOptions) line: 2037
EntityLoader(AbstractEntityLoader).load(SessionImplementor, Object, Object, Serializable, LockOptions) line: 86
EntityLoader(AbstractEntityLoader).load(Serializable, Object, SessionImplementor, LockOptions) line: 76
SingleTableEntityPersister(AbstractEntityPersister).load(Serializable, Object, LockOptions, SessionImplementor) line: 3293
DefaultLoadEventListener.loadFromDatasource(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 496
DefaultLoadEventListener.doLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 477
DefaultLoadEventListener.load(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 227
DefaultLoadEventListener.proxyOrLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 269
DefaultLoadEventListener.onLoad(LoadEvent, LoadEventListener$LoadType) line: 152
SessionImpl.fireLoad(LoadEvent, LoadEventListener$LoadType) line: 1090
SessionImpl.internalLoad(String, Serializable, boolean, boolean) line: 1038
ManyToOneType(EntityType).resolveIdentifier(Serializable, SessionImplementor) line: 630
ManyToOneType(EntityType).resolve(Object, SessionImplementor, Object) line: 438
TwoPhaseLoad.initializeEntity(Object, boolean, SessionImplementor, PreLoadEvent, PostLoadEvent) line: 139
QueryLoader(Loader).initializeEntitiesAndCollections(List, Object, SessionImplementor, boolean) line: 982
QueryLoader(Loader).doQuery(SessionImplementor, QueryParameters, boolean) line: 857
QueryLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean) line: 274
QueryLoader(Loader).doList(SessionImplementor, QueryParameters) line: 2542
QueryLoader(Loader).listIgnoreQueryCache(SessionImplementor, QueryParameters) line: 2276
QueryLoader(Loader).list(SessionImplementor, QueryParameters, Set, Type[]) line: 2271
QueryLoader.list(SessionImplementor, QueryParameters) line: 459
QueryTranslatorImpl.list(SessionImplementor, QueryParameters) line: 365
HQLQueryPlan.performList(QueryParameters, SessionImplementor) line: 196
SessionImpl.list(String, QueryParameters) line: 1268
QueryImpl.list() line: 102