hashcode 和 equals 中能否使用 version 属性


使用乐观锁,在数据库中使用了一个 version 列,对应 Hibernate POJO有一个 version 属性。

在实现 HashCode, equals 方法时是不是不能包括 version 属性呢?



对于如下 POJO 类

public class User {

   private Integer id;

   private String name;

   private String fist_name;

   private String last_name;

   private String password;

   private String password_hint;

   private String email;

   private String address;

   private String city;

   private String post_code;

....

   private Integer version;

...



}

主键是 Id, 关键属性是 name, email ,其它可以为 null。



在 HashCode,equals  中是包含 id,name,email ,还是包含除了 version 之外的所有列?
问题补充:
如果这样的类,该如何确定业务键属性:

public class Road{

   private Integer Id;

   private Set<location> locations = new HashSet<location>();

   private Type type;



  get/set

...

}
问题补充:
Road 类的含议是:

每条道路含有N个地点:locations

每条道路有一种类型,比如国家一级路,省级路等:type



在这种情况下,用locations,type可以确定一条线路。但是《深入浅出 Hibernate》上讲不能使用集合作为业务关键字,否则会引起很多问题。



而 Hibernate 又不能以 id 为业务关键字。那么就只能以Type为关键字,这样的话,根本不能生成有效的 HashCode()和equals()



怎么办?


问题补充:
to mccxj :

很有道理,谢谢。



现在,我想将 roadLocation 加入 Set 集合



public class RoadLocation  implements java.io.Serializable {

private Integer id;

private Road road; //道路对象

private Location location; //地点对象

private Integer mileage; //里程

..

在 HashCode, equals中是否可以

@Override

public boolean equals(Object obj) {

if (this == obj)

return true;

if (getClass() != obj.getClass())

return false;

RoadLocation other = (RoadLocation) obj;

if (location == null) {

if (other.location != null)

return false;

} else if (!location.equals(other.location))

return false;

if (mileage == null) {

if (other.mileage != null)

return false;

} else if (!mileage.equals(other.mileage))

return false;

if (road == null) {

if (other.road != null)

return false;

} else if (!road.equals(other.road))

return false;

return true;

}



@Override

public int hashCode() {

final int prime = 31;

int result = prime

+ ((location == null) ? 0 : location.hashCode());

result = prime * result + ((mileage == null) ? 0 : mileage.hashCode());

result = prime * result + ((road == null) ? 0 : road.hashCode());

return result;

}



这里是使用 road,location 比较,还是使用 road.getName location.getName 比较好呢?


问题补充:
不知我是不是太笨了,竟找不到javaeye回贴的submit,只能不停地用“问题补充”。



to mccxj :

你认为

location.equals(other.location)

road.equals(other.road)

这样的比较正确吗?还是



location.getName().equals(other.location.getName())

road.getName().equals(other.road.getName())



更好呢?

呵呵,下午睡觉和打球去了。。

location.getName().equals(other.location.getName())
road.getName().equals(other.road.getName())

这样的写法,岂不是把location和road的业务都给包含进去了嘛?
这些本来就属于他们的义务,不应该越俎代庖。。。

既然id是主键,为什么还需要别的那?关注中!!

这个要看你的业务,Java对于eqauls方法和hashCode方法是这样规定的:

1、如果两个对象相同,那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同

上面说的对象相同指的是用eqauls方法比较。

在做这样的建模的时候,eqauls方法和hashCode方法一般是跟你的业务模型挂钩的。

1.如果两个对象在业务上是等同的,那么hashcode应该是一样的
2.但是hashcode相同,并不代表两个对象在业务上看起来是一样的

所以你应该是考虑在业务上,这几个属性有没有挂钩,而不是担心所谓的特殊字段。

在针对hibernate来说
[code="java"] 如果你有如下需求,你必须重载 equals() 和 hashCode()方法:

* 想把持久类的实例放入Set中(当表示多值关联时,推荐这么做)
*想重用脱管实例 

Hibernate保证,仅在特定会话范围内,持久化标识(数据库的行)和Java标识是等价的。因此,一旦 我们混合了从不同会话中获取的实例,如果希望Set有明确的语义,就必 须实现equals() 和hashCode()。

实现equals()/hashCode()最显而易见的方法是比较两个对象 标识符的值。如果值相同,则两个对象对应于数据库的同一行,因此它们是相等的(如果都被添加到 Set,则在Set中只有一个元素)。不幸的是,对生成的标识不能 使用这种方法。Hibernate仅对那些持久化对象赋标识值,一个新创建的实例将不会有任何标识值。此外, 如果一个实例没有被保存(unsaved),并且它当前正在一个Set中,保存它将会给这个对象 赋一个标识值。如果equals() 和 hashCode()是基于标识值 实现的,则其哈希码将会改变,这违反了Set的契约。建议去Hibernate的站点阅读关于这个 问题的全部讨论。注意,这不是Hibernate的问题,而是一般的Java对象标识和Java对象等价的语义问题。

我们建议使用业务键值相等(Business key equality)来实现equals() 和 hashCode()。业务键值相等的意思是,equals()方法 仅仅比较形成业务键的属性,它能在现实世界里标识我们的实例(是一个自然的候选码)。 [/code]

以上出自hibernate文档。例子就可以找:

[code="java"]public class Cat {

...
public boolean equals(Object other) {
    if (this == other) return true;
    if ( !(other instanceof Cat) ) return false;

    final Cat cat = (Cat) other;

    if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
    if ( !cat.getMother().equals( getMother() ) ) return false;

    return true;
}

public int hashCode() {
    int result;
    result = getMother().hashCode();
    result = 29 * result + getLitterId();
    return result;
}

}[/code]

注意,业务键不必像数据库的主键那样固定不变.更多的解释,可以参考hibernate的讨论:http://www.hibernate.org/109.html

如何确定业务键属性??

这取决于你的业务是怎样的情况

怎么看你这个road的东西是缺失了一些属性。不应该靠locations来识别road,这已经是另外一个对象了。road难道没有名称之类的属性?

另外,请看清楚,需要重定义的两点理由。

看起来蛮ok的,就是代码丑陋了点。。

给个文章,再研究研究把。
http://www.ibm.com/developerworks/cn/java/j-jtp05273/

location.equals(other.location)
road.equals(other.road)

偶觉得这样就还挺ok