当你想要创建一个将其它域对象保存在SetMap或是List里面的域对象时这是一个问题为了解决这个问题你必须为你的所有对象提供一种equals()和hashCode()的实现这种实现能够保证在它们在对象保存前后正确工作并且当对象在内存中时(返回值)不会改变Hibernate参考文档提供了以下的建议
不要使用数据库标识符来实现等价的判断而应该使用商业键值(business key)一种唯一的通常不改变的属性的结合体当一个buk不可序列化对象(transient object)被持久化的时候数据库标识符会发生改变当一个不可序列化实例(常常和detached instances在一起)被包含在一个Set里面时哈希值的改变会破坏Set的从属关系商业键值的属性并不要求和数据库主键一样稳定你只要保证当对象在某个Set中时它们的稳定性
我们推荐判断商业键值的等价性来实现equals()和hashCode()两个方法这意味着equals()方法只比较能够区分现实世界中的实例的商业键值(某个候选码)的属性(Hibernate 参考文档 v )
换句话说equals()和hashCode()使用商业键值进行处理而对象使用Hibernate生成的键值作为id值这要求对于每个对象有一个相关的不会改变的商业键值可是并不是每个对象类型都有这样的一种键这时候你可能会尝试使用会改变但不时常改变的字段这和商业键值不必和数据库主键一样稳定的思想相吻合当对象在Collection中时候如果这种键不改变那它们似乎就足够好了这是一种危险的主张这意味着你的应用程序可能不会崩溃但是前提是没有人在特定的情况下更新了特定的字段所以应当有一种更好的解决方案而它确实也存在 试图创建和维护在对象和数据库行两者间有着分离的定义的标识符是目前为止讨论的所有问题的根源如果我们统一所有标识符的形式这些问题都将不复存在也就时说作为以数据库为中心和以对象为中心的标识符的替代品我们应该创建一种通用的特定于实体的ID来代表数据实体这种ID应该在数据第一次输入的时候产生无论一个唯一数据实体是保存在数据库是作为对象驻留在内存还时存贮在其它格式的介质中这个通用ID都应该可以识别它通过使用数据实体第一次创建时指派的ID我们可以安全的回到我们对equals()和hashCode()的原始定义它们只是简单地使用了这个id
public class Person {
// assign an id as soon as possible
private String id = IdGeneratorcreateId();
private Integer version;
public String getId() { return id; }
public void setId(String id) {
thisid = id;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
thisversion = version;
}
// Personspecific fields and behavior here
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof Person)) return false;
Person other = (Person)o;
if (id == null) return false;
return idequals(othergetId());
}
public int hashCode() {
if (id != null) {
return idhashCode();
} else {
return superhashCode();
}
}
}
这个例子使用id作为equals()方法判断等价的标准以及hashCode()返回哈希值的来源这就简单了许多但是要让它正常工作我们需要两样东西首先我们需要保证每个对象在被保存之前都有一个id值在这个例子里当id变量被声明的时候它就被指派了一个值其次我们需要一种判断这个对象是新生成的还是之前保存过的的手段在我们最早的例子中Hibernate检查id字段是否为空来判断对象是否时新生成的既然我们的对象id永远不为空这个方法显然不再有效为了解决这个问题我们可以很容易的配置Hibernate让它检查version字段而不是id字段是否为空version字段是一个更为恰当的用来判断你的对象是否被保存过的指示器
[] [] [] [] []