[英]HashCode throws a nullpointer exception
I have a puzzle for you.我有一个谜题给你。
I am making a herb store web app and this is my database:我正在制作一个药草商店 web 应用程序,这是我的数据库:
These are my JPA classes:这些是我的 JPA 类:
public class StoreJPA {
...
@OneToMany(mappedBy="storeJpa", cascade = CascadeType.ALL, orphanRemoval=true, fetch=FetchType.EAGER)
private Set<ProductJPA> specialOffers = new HashSet<ProductJPA>();
...
}
public class ProductJPA {
@ManyToOne
@JoinColumn(name="store_id")
private StoreJPA storeJpa;
@OneToMany(mappedBy="productJpa", cascade = CascadeType.ALL, orphanRemoval=true, fetch=FetchType.EAGER)
private Set<ContainsJPA> contains = new HashSet<ContainsJPA>();
...
private Set<HerbJPA> getHerbs(){
return contains.stream().map(h -> h.getHerbJpa()).collect(Collectors.toSet());
}
@Override
public int hashCode(){
long h = 1125899906842597L; // prime
for(ProductHasHerbJPA phh : contains){
h = 31*h + phh.getHerbJpa().getId();
}
return (int)(31*h + storeJpa.getId());
}
@Override
public boolean equals(Object o){
if(o!=null && o instanceof ProductJPA){
if(o==this)
return true;
return ((ProductJPA)o).getStoreJpa().getId()==storeJpa.getId() &&
((ProductJPA)o).getHerbs().equals(getHerbs()) // compare herbs they contain
}
return false;
}
...
}
public class ContainsJPA {
@Id
private Long id;
@ManyToOne
@JoinColumn(name="product_id")
private ProductJPA productJpa;
@ManyToOne
@JoinColumn(name="herb_id")
private HerbJPA herbJpa;
...
@Override
public int hashCode(){
long h = 1125899906842597L + productJpa.getId(); // <-- nullpointer exception
return (int)(31*h + herbJpa.getId());
}
@Override
public boolean equals(Object o){
if( o != null && o instanceof HerbLocaleJPA) {
if(o==this) {
return true;
}
return ((ProductHasHerbJPA)o).getHerbJpa().getId()==herbJpa.getId() &&
((ProductHasHerbJPA)o).getProductJpa().getId()==productJpa.getId();
}
return false;
}
...
}
Adding a new product with a list of herbs works fine.添加带有草药列表的新产品效果很好。 But when i run this and try to get the products in the store, i get a NullPointerException:
但是当我运行它并尝试在商店中获取产品时,我得到一个 NullPointerException:
java.lang.NullPointerException at com.green.store.entities.ContainsJPA.hashCode(ContainsJPA.java:64) at java.util.HashMap.hash(HashMap.java:339) at java.util.HashMap.put(HashMap.java:612) at java.util.HashSet.add(HashSet.java:220) at java.util.AbstractCollection.addAll(AbstractCollection.java:344) at org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java:327) at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:234) at org.
java.lang.NullPointerException at com.green.store.entities.ContainsJPA.hashCode(ContainsJPA.java:64) at java.util.HashMap.hash(HashMap.java:339) at java.util.HashMap.put(HashMap. java:612) at java.util.HashSet.add(HashSet.java:220) at java.util.AbstractCollection.addAll(AbstractCollection.java:344) at org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java :327) 在 org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:234) 在 org. hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:221) at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:194) at org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl.endLoading(CollectionReferenceInitializerImpl.java:154) at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishLoadingCollections(AbstractRowReader.java:249) at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:212) at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProce
hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:221) at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:194) at org.hibernate.loader.plan.exec. process.internal.CollectionReferenceInitializerImpl.endLoading(CollectionReferenceInitializerImpl.java:154) at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishLoadingCollections(AbstractRowReader.java:249) at org.hibernate.loader.plan.exec. process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:212) 在org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProce ssorImpl.java:133) at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:122) at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86) at org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167) at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4087) at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508) at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventLi
ssorImpl.java:133) at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:122) at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java: 86) at org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167) at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4087) at org.hibernate.event.internal .DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508) 在 org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventLi stener.java:478) at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219) at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:278) at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:121) at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89) at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239) at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1122) at org.hibernate.type.EntityType.resolveIdentifier(Ent
stener.java:478) at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219) at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:278) at org.hibernate.event .internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:121) at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89) at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239) at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1122) at org.hibernate.Entifier.Entifier.EntityType ityType.java:672) at org.hibernate.type.EntityType.resolve(EntityType.java:457) at org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:165) at org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:125) at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:238) at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:209) at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:133) at org.hibernate
ityType.java:672) at org.hibernate.type.EntityType.resolve(EntityType.java:457) at org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:165) at org.hibernate.engine.internal .TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:125) at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:238) at org.hibernate.loader.plan.exec.process.internal .AbstractRowReader.finishUp(AbstractRowReader.java:209) at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:133) at org.hibernate .loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:122) at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86) at org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167) at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4087) at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508) at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478) at org.hibernate.e
.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:122) at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86) at org.hibernate.loader.entity .plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167) at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4087) at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508 ) 在 org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478) 在 org.ZCB1F008EEBF5012C4EF9A2C36E574D6 vent.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219) at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:116) at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89) at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239) at org.hibernate.internal.SessionImpl.immediateLoad(SessionImpl.java:1097)...
vent.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219) at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:116) at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java: 89) at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239) at org.hibernate.internal.SessionImpl.immediateLoad(SessionImpl.java:1097)...
The hashCode function of ContainsJPA throws this exception when getting the id for the product. ContainsJPA 的 hashCode function 在获取产品的 id 时会抛出此异常。 Why is it doing that, considering that the 'contains' table in the DB has this id?
考虑到数据库中的“包含”表具有此 ID,为什么要这样做? I can't figure out why this is happening.
我无法弄清楚为什么会这样。 Please help.
请帮忙。
Your hashCode and equals implementations are incorrect.您的 hashCode 和 equals 实现不正确。
The problems with it, in a nutshell:简而言之,它的问题是:
Both hashCode and equals are specced to require that you do not throw NPEs out of them. hashCode 和 equals 都要求您不要将 NPE 扔出。 For equals, that means you can't just call, say,
a.equals(b)
- you'd have to make that a == null? b == null: a.equals(b)
对于equals,这意味着你不能只调用
a.equals(b)
- 你必须让a == null? b == null: a.equals(b)
a == null? b == null: a.equals(b)
(and because this 'never throw' is transitive, a.equals(b)
is fine, even if b is null), or use the helper Objects.equal(a, b)
instead. a == null? b == null: a.equals(b)
(并且因为这个“从不抛出”是传递性的, a.equals(b)
很好,即使 b 为空),或者使用帮助器Objects.equal(a, b)
反而。
For hashcode, it means that null values must be defined as having some predefined value for the sake of hashing.对于哈希码,这意味着必须将 null 值定义为具有一些预定义值以便进行哈希处理。 Also, more generally, whenever you have a 'sub object' (eg a field of some non-primitive type', the general idea is for hashCode and equals to cascade: Use
productJPA.hashCode()
and not productJPA.getId()
.此外,更一般地说,每当您有一个“子对象”(例如,一些非原始类型的字段)时,一般的想法是 hashCode 并且等于级联:使用
productJPA.hashCode()
而不是productJPA.getId()
。
Same goes for equals.平等也一样。 Don't do this:
不要这样做:
(ProductHasHerbJPA)o).getHerbJpa().getId()==herbJpa.getId()
but do this:但这样做:
Objects.equals(o.getHerbJpa(), herbJpa);
And if 2 herb JPAs are to be considered equal if their IDs are equal, then the HerbJPA class's equals()
method should be defined accordingly, and if not, then not.如果 2 个草本 JPA 的 ID 相等,则认为它们相等,则应相应定义 HerbJPA 类的
equals()
方法,如果不相等,则不定义。 It is not the job of your ContainsJPA class to know how to calculate if 2 herbJPA instances are equal - herbJPA can do that, itself.知道如何计算 2 个herbJPA 实例是否相等不是您的 ContainsJPA class 的工作——herbJPA 本身可以做到这一点。 In passing you avoid a ton of null issues by doing it this way.
通过这种方式,您可以避免大量 null 问题。
Note, you can let lombok take care of all this boilerplate for you.请注意,您可以让lombok为您处理所有这些样板文件。
Next, we get to some hairy issues with JPA and equality in particular.接下来,我们会遇到一些关于 JPA 的棘手问题,尤其是平等问题。
The common strategy to do equals/hashCode in the java ecosystem (outside of JPA/Hibernate) is to look at all fields that are part of an object's identity, which is usually all of them.在 java 生态系统(JPA/Hibernate 之外)中执行 equals/hashCode 的常用策略是查看作为对象身份一部分的所有字段,通常是所有字段。 The problem is, that doesn't work well with JPA: Most of the getter methods on a JPA object are proxies which cause DB queries if you invoke them.
问题是,这不适用于 JPA:JPA object 上的大多数 getter 方法都是代理,如果您调用它们会导致数据库查询。 With a sufficiently interconnected db structure (lots of references), that means a single
equals
call ends up querying half your DB, takes a ton of memory, and half an hour to complete, obviously not a feasible solution.使用充分互连的数据库结构(大量引用),这意味着单个
equals
调用最终会查询一半的数据库,需要大量的 memory 和半小时才能完成,显然不是一个可行的解决方案。
The key question is: What does your object actually represent, and as far as I know, JPA does not give clear guidance.关键问题是:你的object究竟代表什么,据我所知,JPA并没有给出明确的指导。
Then we can draw the following conclusions:那么我们可以得出以下结论:
if (this == other) return true;
if (this == other) return true;
. This view incidentally is also convenient in that you entirely avoid that 'whoops it queries the entire DB' issue.顺便说一下,这个视图也很方便,因为您完全避免了“哎呀它查询整个数据库”的问题。 unids are not expensive to fetch, and are usually prefetched already.
unid 的获取成本并不高,而且通常已经预取。
If this is the case, may I suggest your class is misnamed?如果是这种情况,我可以建议您的 class 名称错误吗? It should be 'Herb', probably.
应该是“草本”吧。 Maybe 'HerbJpa' (NB: JPA in all-caps is a violation of the most common style rule).
也许“HerbJpa”(注意:全大写的 JPA 违反了最常见的样式规则)。
Then the most sensical solution is to AVOID checking the unid entirely, and look only at all the other fields (or at least, all the other ones that represent something about the herb's identity. This is usually most of them, but sometimes you can get away with defining some property that would cause a storm of DB queries, such as 'a list of associated herbs', represented in the DB with a join table, as 'not part of the identity'. After all, 'the unid in the db' is an incidental implementation detail of the notion of a 'herb' and therefore couldn't possibly be part of the identity of it!然后最明智的解决方案是避免完全检查unid,而只查看所有其他字段(或者至少,所有其他代表草药身份的字段。这通常是其中的大多数,但有时你可以得到不再定义一些会导致数据库查询风暴的属性,例如“关联草药列表”,在数据库中用连接表表示,作为“不是身份的一部分”。毕竟,“ db' 是“草本”概念的附带实现细节,因此不可能成为它的身份的一部分!
The downside of this view is of course that 'storm of DB calls' issue.这种观点的缺点当然是“数据库调用风暴”问题。
Generally I advise you treat these objects as representing 'row in a table' and not 'the actual herb', in which case, your equals and hashCode methods become relatively simple, and the name of the class is fine (well, it should be 'Jpa', not 'JPA', but other than that).一般来说,我建议您将这些对象视为代表“表格中的行”而不是“实际的药草”,在这种情况下,您的 equals 和 hashCode 方法变得相对简单,并且 class 的名称很好(嗯,应该是'Jpa',不是'JPA',但除此之外)。
@Override public int hashCode() {
return id == null ? super.hashCode() : (int) id;
// note, other answer's id %1000 is silly;
// it is needlessly inefficient, don't do it that way.
}
@Override public boolean equals(Object other) {
if (other == this) return true;
if (other == null || other.getClass() != ContainsJPA.class) return false;
return id == null ? false : id.equals(other.id);
}
Not 100% sure, but doesn't AbstractRowReader first load the collection and then "hydrate" the associated entities?不是 100% 肯定,但 AbstractRowReader 不是首先加载集合然后“水合”关联实体吗?
AbstractRowReader#finishUp() AbstractRowReader#finishUp()
...
// now we can finalize loading collections
finishLoadingCollections( context );
// finally, perform post-load operations
postLoad( postLoadEvent, context, hydratedEntityRegistrations, afterLoadActionList
);
Which means when creating the collection, the product_id is known, but the ProductJPA instance hasn't been hydrated yet.这意味着在创建集合时,product_id 是已知的,但 ProductJPA 实例尚未水合。
tbh, I think it's not great practice to derive a hashcode from associated entities. tbh,我认为从关联实体派生哈希码不是很好的做法。 I'd probably do something like
我可能会做类似的事情
public class ContainsJPA {
@Id
private Long id;
@Override
public int hashCode(){
return id == null ? super.hashCode() : id % 1000;
}
to get some distribution (the '1000' is a magic number, depending on what typical collection sizes are).获得一些分布(“1000”是一个神奇的数字,取决于典型的集合大小)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.