在hibernate中将散列函数委托给未初始化的委托会导致更改hashCode
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在hibernate中将散列函数委托给未初始化的委托会导致更改hashCode相关的知识,希望对你有一定的参考价值。
我有一个问题,hashCode()
使用hibernate委托给未初始化的对象。
我的数据模型看起来如下(以下代码经过高度修剪以强调问题因此破坏,不要复制!):
class Compound {
@FetchType.EAGER
Set<Part> parts = new HashSet<Part>();
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()
的实施完全遵循in the hibernate documentation给出的建议。
现在,如果我加载Compound
类型的对象,它会急切地加载HasSet
和部件。这会调用部件上的hashCode()
,然后调用化合物上的hashCode()
。但问题是,此时并非所有考虑用于创建复合的hashCode的值都可用。因此,部件的hashCode在初始化完成后改变,从而制动HashSet
的契约并导致各种难以跟踪的错误(例如,在部件组中设置两次相同的对象)。
所以我的问题是:避免这个问题的最简单的解决方案是什么(我想避免为自定义加载/初始化编写类)?我完全做错了吗?
编辑:我错过了什么吗?这似乎是一个基本问题,为什么我在任何地方都找不到任何关于它的东西?
您应该使用一组用于标识各个对象的equals()属性,而不是使用数据库标识符进行相等性比较。 [...]无需使用持久性标识符,所谓的“业务密钥”要好得多。这是一个自然的关键,但这次使用它没有错! (article from hibernate)
和
建议您使用Business key equality实现equals()和hashCode()。业务键等式意味着equals()方法仅比较形成业务键的属性。它是识别我们在现实世界中的实例的关键(自然候选键)。 (hibernate documentation)
编辑:这是加载发生时的堆栈跟踪(如果这有帮助)。在那个时间点,属性someUniqueName
为null,因此hashCode被错误地计算。
Compound.getSomeUniqueName() line: 263
Compound.hashCode() line: 286
Part.hashCode() line: 123
HashMap<K,V>.put(K, V) line: 372
HashSet<E>.add(E) line: 200
HashSet<E>(AbstractCollection<E>).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
<my code where the query is executed>
你有一个完美的合法用例,事实上它应该有效。但是,如果在设置'someUniqueName'之前设置Compound对象的'parts',那么在常规Java中会遇到同样的问题。
因此,如果你能说服hibernate在'parts'属性之前设置'someUniqueName'属性。您是否尝试过在java类中重新排序它们?或者将“部分”重命名为“zparts”? hibernate文档只是说订单不能保证。我在hibernate中提交了一个错误,允许强制执行此命令...
另一种可能更容易的解决方案:
class Part {
public int hashCode() {
//don't include getCompound().hashCode()
return getSomeUniqueName() == null ? 0 : getSomeUniqueName().hashCode();
}
public boolean equals(Object o)
{
if (this == o) return true;
if (!o instanceof Part) return false;
Part part = (Part) o;
if (getCompound() != null ? !getCompound().equals(part.getCompound()) : part.getCompound()!= null)
return false;
if (getSomeUniqueName()!= null ? !getSomeUniqueName().equals(part.getSomeUniqueName()) : part.getSomeUniqueName()!= null)
return false;
return true;
}
}
在Compound.equals()中,确保它也以
public boolean equals(Object o)
{
if (this == o) return true;
这应该避免你现在遇到的问题。
hashCode()方法中的每个属性都应该在equals()方法中,但不一定是相反的方式。
从您的问题我明白,所有参与hashCode()
方法的模型属性都没有默认加载。在这种情况下,如果您想要加载所有属性,那么您可以按照方法进行操作。
- 通过调用模型类的
hashCode()
中的getter方法,因为它初始化/加载所有模型属性。 - 通过使用
sesstion.get()
而不是session.load()
方法,因为它不会创建任何代理并将加载模型的所有属性。 - 通过为映射中的所有属性设置
lazy="false"
。
希望这可以解决您的问题!
我在其中一条评论中读到了你让Eclipse生成equals
和hashCode
方法的问题。
你是否为所有实体(Part
和Compound
)做到了这一点?我问,因为如果是这种情况,那些方法通常直接访问对象属性(即不调用getter方法)。它们看起来如下。
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((prop == null) ? 0 : prop.hashCode());
return result;
}
当使用Hibernate时,这通常会导致类似你所描述的问题,因为未初始化的属性具有默认值(对象为null
,int
s为0,依此类推),直到调用适当的get方法,这会导致hibernate代理访问数据库并加载计算方法的正确值所需的值。
如果启动调试器并在第一次调用hashCode()
时检查属性,则可以轻松发现问题。
如果发生这种情况,解决此问题的最简单方法是修改您的方法以使用get方法,如下所示:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getProp() == null) ? 0 : getProp().hashCode());
return result;
}
值得注意的另一点是:Eclipse生成的equals方法包含执行此检查getClass() != obj.getClass()
,它不适用于由Hibernate代理扩展的Hibernate实体。我会用instanceof
支票替换它。
那么为什么不使用列表而不是一组?我知道这将是一个解决方法,而不是一个正确的解决方案,但你根本不必使用哈希码。
我想到的一种可能性是遵循this article给出的建议。他们基本上建议不要使用hibernate(或者更确切地说是数据库)来生成ID,而是使用UUID库来生成自己的ID,然后将这些ID用于equals()
和hashCode()
。除了本文中提到的问题,它对我当前的实现有一些严重的缺点:它将破坏我现有的代码!每次我创建一个Part
实例时,我首先要查询它是否已经存在于数据库中并检查它是否存在。在我目前的实现中,我只是按照自己喜欢的方式创建Parts
,然后将它们添加到Compound
中。如果Compound
已经有了这样的一部分,一切都会自动完成......
我发现了一个相关的问题here。解决方案的基本思想是,一旦您不急切地获取部件,整个问题就会消失。然后在加载零件时,化合物已完全初始化。但是,当在具有分离对象的会话之外工作时,这会打开一个完全不同的问题...
以上是关于在hibernate中将散列函数委托给未初始化的委托会导致更改hashCode的主要内容,如果未能解决你的问题,请参考以下文章