HashCode引发空指针异常

拉撒路斯:

我为你感到困惑。

我正在制作一家药草店网络应用程序,这是我的数据库:

在此处输入图片说明

  • 商店可以有很多产品
  • 一个产品可以包含许多草药

这些是我的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;
    }
...
}

添加带有草药列表的新产品效果很好。但是当我运行它并尝试在商店中购买产品时,我得到了NullPointerException:

com.green.store.entities.ContainsJPA.hashCode(ContainsJPA.java:64)处的java.lang.NullPointerException java.util.HashMap.hash(HashMap.java:339)处的java.util.HashMap.put(HashMap。 org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java)的java.util.AbstractCollection.addAll(AbstractCollection.java:344)的java.util.HashSet.add(HashSet.java:220)的java:612) :327),位于org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:234),位于org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:221),位于org.hibernate。 org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl上的engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:194)。org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishLoadingCollections(AbstractRowReader.java:249)的org.hibernate.loader.plan.exec.process.internal.AbstractRowReader的endLoading(CollectionReferenceInitializerImpl.java:154) org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:133)的finishUp(AbstractRowReader.java:212) org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86)处的org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167)处的AbstractLoadPlanBasedLoader.java:122)在org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4087)在org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)在org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478)在org.hibernate.event.internal.DefaultLoadEventListener org.org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:278)的org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:121)的.load(DefaultLoadEventListener.java:219)在org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239)处的hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)在org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1122)位于org.hibernate.type处的org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:672)处。org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:165)的EntityType.resolve(EntityType.java:457)org.org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:125)的EntityType.resolve(EntityType.java:457) org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:238)在org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:209)在org org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:122)处的.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:133) org.hibernate.loader.entity.plan的.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86)。org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4087)的org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)的AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167) .org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478)在org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)在org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener) .java:116),位于org.hibernate.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89),位于org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239),位于org.hibernate.internal.SessionImpl。 InstantLoad(SessionImpl.java:1097)...在org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4087)处org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)处加载(AbstractLoadPlanBasedEntityLoader.java:167) org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)的org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java)的.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478) :116)在org.hibernate.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)在org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239)在org.hibernate.internal.SessionImpl.immediateLoad( SessionImpl.java:1097)...在org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4087)处org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)处加载(AbstractLoadPlanBasedEntityLoader.java:167) org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)的org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java)的.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478) :116)在org.hibernate.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)在org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239)在org.hibernate.internal.SessionImpl.immediateLoad( SessionImpl.java:1097)...org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)上的persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4087)在org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java: 478)在org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)在org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:116)在org.hibernate.event.internal.DefaultLoadEventListener在org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239)的.onLoad(DefaultLoadEventListener.java:89)在org.hibernate.internal.SessionImpl.immediateLoad(SessionImpl.java:1097)的...org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)上的persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4087)在org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java: 478)在org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)在org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:116)在org.hibernate.event.internal.DefaultLoadEventListener在org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239)的.onLoad(DefaultLoadEventListener.java:89)在org.hibernate.internal.SessionImpl.immediateLoad(SessionImpl.java:1097)的...org的org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478)的org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)的DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508) .hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:116)在org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)在org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java) :1239),位于org.hibernate.internal.SessionImpl.immediateLoad(SessionImpl.java:1097)...org的org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478)的org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)的DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508) .hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:116)在org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)在org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java) :1239),位于org.hibernate.internal.SessionImpl.immediateLoad(SessionImpl.java:1097)...在org.hibernate的org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:116)处加载(DefaultLoadEventListener.java:219)在org.hibernate的org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)处加载org.hibernate.internal.SessionImpl.immediateLoad(SessionImpl.java:1097)的.internal.SessionImpl.fireLoad(SessionImpl.java:1239)...在org.hibernate的org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:116)处加载(DefaultLoadEventListener.java:219)在org.hibernate的org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)处加载org.hibernate.internal.SessionImpl.immediateLoad(SessionImpl.java:1097)的.internal.SessionImpl.fireLoad(SessionImpl.java:1239)...

获取产品的ID时,ContainsJPA的hashCode函数将引发此异常。考虑到数据库中的“包含”表具有此ID,为什么这样做呢?我不知道为什么会这样。请帮忙。

rzwitserloot:

您的hashCode和equals实现不正确。

简而言之,它的问题是:

  • 他们不遵循“委托”风格(他们不将确定平等的工作委托给相关的阶级)
  • 他们没有回答对象代表什么的中心问题:数据库中的行,或数据库中的行试图表示的概念。

委托平等检查

hashCode和equals均要求您不要将NPE丢掉。对于equals,这意味着您不能只打电话说,a.equals(b)-您必须做到这一点a == null ? b == null : a.equals(b)(并且因为“永不抛出”是可传递的,a.equals(b)所以即使b为null也可以),或者使用辅助函数Objects.equal(a, b)

对于哈希码,这意味着为了哈希,必须将空值定义为具有一些预定义的值。同样,更一般而言,每当您具有“子对象”(例如某些非原始类型的字段)时,通常的想法是使用hashCode并等同于级联:Use productJPA.hashCode()和not productJPA.getId()

平等同样适用。不要这样做:

(ProductHasHerbJPA)o).getHerbJpa().getId()==herbJpa.getId()

但是这样做:

Objects.equals(o.getHerbJpa(), herbJpa);

如果两个草本JPA的ID相等,则认为它们相等,那么equals()应相应定义HerbJPA类的方法,否则,则定义不。知道如何计算2个HerbJPA实例是否相等不是您的ContainsJPA类的工作-HerbJPA本身可以做到这一点。通过这样做,您避免了很多空问题。

请注意,您可以让龙目岛为您处理所有这些样板。

接下来,我们讨论一些JPA尤其是平等问题。

在Java生态系统中(在JPA / Hibernate之外),执行equals / hashCode 常见策略是查看属于对象标识一部分的所有字段,通常都是这些字段。问题是,它不适用于JPA:JPA对象上的大多数getter方法都是代理,如果您调用它们,则会导致数据库查询。通过充分互连的数据库结构(大量引用),这意味着单个equals调用最终将查询您的数据库的一半,占用大量内存,并且需要半小时才能完成,这显然不是可行的解决方案。

关键问题是:对象实际上代表什么,据我所知,JPA没有给出明确的指导。

HerbsJPA的实例代表数据库中的一行

然后我们可以得出以下结论:

  • 一如既往,根据规范,对象始终等于自身:if (this == other) return true;除此以外...
  • 如果两个对象中的一个或两个都没有设置unid,则它们不能彼此相等 -2个未写的行,即使对象中的每个字段完全相同,也仍不代表“同一行”,因此不相等!
  • 如果两个对象都有一个设置的unid,则如果unid是相等的,则它们相等;否则,它们不是相等的。不管其他所有值!-2个具有相同值的不同行仍然是两个不同的行。

顺便提一下,这种视图也很方便,因为您完全避免了“ whoops it query the整个数据库”的问题。unid的获取并不昂贵,通常已经进行了预取。

HerbsJPA的一个实例代表“草药”。

如果是这种情况,我是否可以建议您的班级命名错误?应该是“ Herb”。也许是“ HerbJpa”(注意:大写的JPA违反了最常见的样式规则)。

那么最无意义的解决方案是AVOID检查UNID完全,只有在所有其他领域看(或者至少,所有其他的人,代表一些关于草药的身份。这通常他们的最多的是,但有时你可以得到定义了一些会引起数据库查询风暴的属性,例如“一个关联草药列表”(在数据库中用联接表表示)为“不是身份的一部分”。 db”是“ herb”概念的附带实现细节,因此不可能成为其标识的一部分!

这种观点的缺点当然是“数据库调用风暴”问题。

通常,我建议您将这些对象视为代表“表中的行”而不是“实际的草本植物”,在这种情况下,您的equals和hashCode方法变得相对简单,并且类的名称很好(嗯,应该是“ 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);
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章