论坛首页 Java企业应用论坛

实现实体的hashCode,equals时候请注意

浏览 46772 次
该帖已经被评为精华帖
作者 正文
   发表时间:2004-11-21  
看来你是给我下套了,要用hibernate映射多态会不会有重复ID我倒是不记得了,不是三种策略么?懒得去看,我没法做肯定或者否定。另外多态映射的ID可以手动分配么,手动的时候会不会重复?但这不重要,关键是我的多态树为什么一定树依赖Hibernate来做出这种多态继承映射(有些时候记得我不喜欢他的任何方式),我狠可能是自己每个建个表。我说的isNew并不是特指的那个方法,说的是判断是否为持久过的对象而已。。。

即使不以这个多态都没关系,比如非常多的情况都可能把不同的实体类放到同样的散列表之中,如果不能区别类型就会相互覆盖了。。。

希望不是全在老兄的意料之中,我怕和aggressive的家伙说话,:D
0 请登录后投票
   发表时间:2004-11-21  
Readonly 写道
这篇wiki确实很详细,但是它有一个明显的错误:没有列出它用eq/hC with the id property实现的代码,从测试代码上看,在id为null的时候,它的hashCode是相等的,所以才会出现multiple new instances in set不能pass的错误。

而改成用这样的实现策略就能都pass了:
......


  readonly,你这isNew实现也倒"新颖",因为除了这个词,我实在想不出更加礼貌的词语了.

   何谓丑陋,不至于我用一种id策略,就得写个isNew之类的超类实现就是优雅了吧.

    两个实体对象其中一个id为null,就不管三七二十一认为两个不相等. 这是什么逻辑? 或许你可以让存取代码正确,但你这个对象算什么回事?

    我们不该为了hibernate而hibernate,更多的,我们是为了让你这个entity处理起来更加象java对象.
   
    而且,isNew的实现,在我看来,是丑陋的.不可靠的. 除了hibernate, 除了hibernate的这种id生成策略,你敢保证你这个对象能够正确存取吗? 多累啊,想来想去,还是要小心点好, 不如一次性的用上你所说的"丑陋"代码,他虽然"丑陋",但他似乎能够应付一切.

   哼哼...............
0 请登录后投票
   发表时间:2004-11-22  
Readonly 写道
shenli 写道
:P 如果是多态继承树你觉得会不会,有无可能?而且作为统一父类本来就应该考虑各种情况。如果当前不出错,只要没用到,isNew我也可以不要

就知道你要这么说, 偶反问你:多态继承你在Hibernate里面怎么mapping, 会出现id相同的吗?

为什么偶这里有一个isNew()的public方法就是另外的故事了, 和本次讨论无关,偶们在有定论以前先别离题,哼哼



    hibernate的id生成策略倒是没有仔细研究,但在其他的一些映射工具多态多表的情况下,id完全可以一样.


    仔细想想,hibernate中,即使多态多表的情况下,id生成策略是类层次唯一,那么你的DAO有多大意义?   反正目前只有知道hibernate支持你的想法, 换了别的工具,你的想法就不一样了,还不如直接写到你的代码中,或者不需要DAO,而是直接来个hibernateDAO吧...........
     dao没有记错的话,主要的功能是为了封装存取代码的.
0 请登录后投票
   发表时间:2004-11-22  
完蛋了,这篇有变成大肠贴的趋势了,偶尝试着拉回来吧,先整理一下来龙去脉:
1. weihello说: many-to-many的时候要注意equals()/hashCode()的写法

2. CafeBabe说: 简单地采用ID做为equals()/hashCode()实现策略不就可以解决weihello的问题了么?

3. zingers说: 靠ID比较是不可靠的,如果2个Object的ID为null, 但是实际意义上是相当的, 岂不是错误了? (开始离题了一次)

4. 偶说: 如果2个Object的ID为null, 哪怕它的业务属性的值相当, 偶也认为它们是不等的, 这样做有什么错误么? (注意, 后面的回帖没有人说过偶的这个想法有什么逻辑错误, sigh)

5. weihello回来了: 举出了一篇hibernate网站上的wiki, http://www.hibernate.org/109.html , 来说明基于ID做equals()/hashCode() 是有问题的, 不能满足其中的2个测试. (嗯, weihello是个好同志, 把主题拉回来了)

6. 偶回来了: weihello举了一篇错误的wiki来证明他的观点, 该论据不成立, 用CafeBabe最初给出的代码就能完全通过那4个测试, 偶不怕麻烦地重新贴了一下代码, 建议weihello去测试一下, 而不应该是wiki上说什么就信什么.

7. weihello又回来了: 开始扯一大堆了......

好了,别扯那么多,偶只想讨论的是: 在Hibernate世界里面, 偶们应该如何采用一个简单,有效的策略来实现equals()/hashCode(), that's all...... 偶的Hibernate in action这本书还没有到, 有看过的人可以说说它里面推荐的做法, kaka.

weihello 写道

两个实体对象其中一个id为null,就不管三七二十一认为两个不相等. 这是什么逻辑?

偶也不知道这是一种什么逻辑, 你能举个实际用例的代码来说明它有什么逻辑错误么?
0 请登录后投票
   发表时间:2004-11-22  
Readonly 写道
完蛋了,这篇有变成大肠贴的趋势了,偶尝试着拉回来吧,先整理一下来龙去脉:
weihello 写道

两个实体对象其中一个id为null,就不管三七二十一认为两个不相等. 这是什么逻辑?

偶也不知道这是一种什么逻辑, 你能举个实际用例的代码来说明它有什么逻辑错误么?


   呵呵,这就是所谓的仰视光芒了。
这篇wiki在我看来是没有你所说错误的,总比实现不伦不类的isNew强吧。

weihello 写道

两个实体对象其中一个id为null,就不管三七二十一认为两个不相等. 这是什么逻辑?

    这给代码我想不用贴了吧,难道代码中永远不会出项往map或者set之类中添加两个id为null,实际属性一摸一样的代码?

   呵呵,我并非不打算仰视你的光芒。 只是,我想说明的是,

一方面你这种方式会出现以上之类的问题,

一方面你这样写代码不好移植、扩展。


   希望你能够理性看待这个问题,你坚持的这种做法是按了葫芦起了瓢,得不偿失。

   如果认为我在拉大肠的话,无话可说,只好保持沉默。
0 请登录后投票
   发表时间:2004-11-22  
偶错了, 对于误导观众表示歉意: 按照前面的写法, 那篇文章里面的test, 只能pass前面3个

第4个不能pass:
引用

collections intact after saving:

Will the following work or not:
HashSet set = new HashSet();;
User u = new User();;
set.add(u);;
session.save(u);;
assert(set.contains(u););;



和CafeBabe说的一摸一样, 那么需要反思为什么偶在实际项目中没有遇到过这种用法那? 再想想看有没有其他的方法来通过这个test. 
0 请登录后投票
   发表时间:2004-11-22  
嗯, 偶又回来了, 想了一个方法来解决原来的问题: 原先基于ID的hashCode()实现, 会在object被持久化以后, ID从null改变到了一个值, 这样hashCode就随之发生了改变, 这个是不符合hashCode语义的地方, 那么偶们来加一个小小的trick, 让它保持不变:
public abstract class Entity implements Serializable {
    private Long id;
    private Integer trickHashCode;

    public boolean isNew(); {
        return id == null;
    }

    public boolean equals(Object o); {
        if (this == o);
            return true;

        if (o == null);
            return false;

        if (!(o instanceof Entity););
            return false;

        if (this.isNew(); || ((Entity); o);.isNew(););
            return false;

        return (id.equals(((Entity); o);.getId();););;
    }

    public int hashCode(); {
        if (trickHashCode == null); {
            if (isNew(););
                trickHashCode = new Integer(super.hashCode(););;
            else
                trickHashCode = new Integer(id.hashCode(););;
        }
        return trickHashCode.intValue();;
    }
}


这样就pass CafeBabe说的问题(collections intact after saving)了.

以下是unit test pass那个wiki里面说的几种情况:
    public void test(); {
        Parent p1 = new Parent();;
        Child c1 = new Child();;
        Child c2 = new Child();;
        p1.addChild(c1);;
        p1.addChild(c2);;
        
        //multiple new instances in set:
        assertEquals(2, p1.getChildren();.size(););;
        manager.save(p1);;        
        
        //equal to same object from another session:
        Parent p2 = manager.load(p1.getId(););;
        assertEquals(p1, p2);;

        //collections intact after saving:
        Child c3 = new Child();;
        p2.addChild(c3);;
        manager.save(p2);;
        assertEquals(3, p2.getChildren();.size(););;
        assertTrue(p2.getChildren();.contains(c3););;
    }


这种简单有效的做法, 目前看起来就只有一个问题了: 这种先save再load一次, 然后和之前的未被持久化的object做mix compare不能通过,
        Parent p1 = new Parent();;
        Child c1 = new Child();;
        p1.addChild(c1);;
        manager.save(p1);;        
        
        Parent p2 = manager.load(p1.getId(););;        
        //will fail......
        assertTrue(p2.getChildren();.contains(c1););;

实在是想不出来这种比较会出现在什么情境下了......
0 请登录后投票
   发表时间:2004-11-22  
无论优雅于丑陋,符合实际需要就好了。
这个世界上有成千上万的DIRTY CODE在正常而出色的完成自己的使命。

至于这个BasePersistentObject我真不敢想象它在实际应用中的性能,太恐怖了~
0 请登录后投票
   发表时间:2004-11-22  
Readonly,你的后面的这种写法在特定场合不管会不会遇到错误,都已经违反了hashCode基本的原则,当两个对象equals方法成立的时候,必须得到同样的散列码

既然一个对象是可变的,我不知道为什么要保证他的散列码不变呢?这个测试(当然不知道谁规定的)这种可重新读出有意义吗?本来在java环境下就没这个要求,不过是hibernate一种特殊的主键产生策略导致的问题?

刚刚download了hibernate in acdtion的代码,他是在每个entity用业务字段生成的equals和hashCode。原来我也是在一个父类或者mixin中用主键做的,唯一的不同就是用this.getclass == object.getclass取代你的x instance of xxxx
0 请登录后投票
   发表时间:2004-11-23  
shenli 写道
Readonly,你的后面的这种写法在特定场合不管会不会遇到错误,都已经违反了hashCode基本的原则,当两个对象equals方法成立的时候,必须得到同样的散列码

嗯, 偶做错了, 这样会导致: 先做了hashCode操作以后一个未被持久化的object, 和再从数据库里面获得的object虽然equals, 但是hashCode不一样了, 违反了最基本的原则, 

shenli 写道
既然一个对象是可变的,我不知道为什么要保证他的散列码不变呢?这个测试(当然不知道谁规定的)这种可重新读出有意义吗?本来在java环境下就没这个要求,不过是hibernate一种特殊的主键产生策略导致的问题?

嗯, java规范里面好像是没有这样的规定, 但是作为一种特定的可持久化对象 (如hibernate这样对应到数据库里的一个row), 在equals的语义上带来的后果: 2个从不同session里面load的对应到同一row的object, 你认为它们是相等还是不相等呢?

shenli 写道
刚刚download了hibernate in acdtion的代码,他是在每个entity用业务字段生成的equals和hashCode。原来我也是在一个父类或者mixin中用主键做的,唯一的不同就是用this.getclass == object.getclass取代你的x instance of xxxx

昨天偶问过看过Hibernate in action的人了, 他说书里面有3种做法:
1. 就是偶说的这种用无意义主键做hashCode/equals
2. 就是weihello用的所有值做hashCode/equals
3. 用一个(或者几个)相对稳定的业务字段做hashCode/equals (比如user, 就用userName).

hibernate 推荐的是第3种, 按照这种推荐的做法, 就不会出现以上说的所有问题了, 看起来是最佳的实践了.
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics