Hibernate检查id字段实例讲解

来源:岁月联盟 编辑:zhuzhu 时间:2009-09-23

学习Hibernate时,经常会遇到id字段问题,这里将介绍问题的解决方法——Hibernate检查id字段。

当你想要创建一个将其它域对象保存在Set,Map或是List里面的域对象时,这是一个问题。为了解决这个问题,你必须为你的所有对象提供一种equals()和hashCode()的实现,这种实现能够保证在它们在对象保存前后正确工作并且当对象在内存中时(返回值)不会改变。Hibernate参考文档提供了以下的建议:

“不要使用数据库标识符来实现等价的判断,而应该使用商业键值(business key),一种唯一的,通常不改变的属性的结合体。当一个buk不可序列化对象(transient object)被持久化的时候,数据库标识符会发生改变。当一个不可序列化实例(常常和detached instances在一起)被包含在一个Set里面时,哈希值的改变会破坏Set的从属关系。商业键值的属性并不要求和数据库主键一样稳定,你只要保证当对象在某个Set中时它们的稳定性。

“我们推荐判断商业键值的等价性来实现equals()和hashCode()两个方法。这意味着equals()方法只比较能够区分现实世界中的实例的商业键值(某个候选码)的属性。“(Hibernate 参考文档 v. 3.1.1).

换句话说,equals()和hashCode()使用商业键值进行处理,而对象使用Hibernate生成的键值作为id值。这要求对于每个对象有一个相关的不会改变的商业键值。可是,并不是每个对象类型都有这样的一种键,这时候你可能会尝试使用会改变但不时常改变的字段。这和商业键值不必和数据库主键一样稳定的思想相吻合。当对象在Collection中时候如果这种键不改变,那它们似乎就“足够好”了。这是一种危险的主张,这意味着你的应用程序可能不会崩溃,但是前提是没有人在特定的情况下更新了特定的字段。所以,应当有一种更好的解决方案,而它确实也存在。试图创建和维护在对象和数据库行两者间有着分离的定义的标识符是目前为止讨论的所有问题的根源。如果我们统一所有标识符的形式,这些问题都将不复存在。也就时说,作为以数据库为中心和以对象为中心的标识符的替代品,我们应该创建一种通用的,特定于实体的ID来代表数据实体,这种ID应该在数据第一次输入的时候产生。无论一个唯一数据实体是保存在数据库,是作为对象驻留在内存,还时存贮在其它格式的介质中,这个通用ID都应该可以识别它。通过使用数据实体第一次创建时指派的ID,我们可以安全的回到我们对equals()和hashCode()的原始定义。它们只是简单地使用了这个id:

  1. public class Person {   
  2. // assign an id as soon as possible   
  3. private String id = IdGenerator.createId();   
  4. private Integer version;  
  5. public String getId() { return id; }   
  6. public void setId(String id) {   
  7. this.id = id;  
  8. }  
  9. public Integer getVersion() {  
  10. return version;  
  11. }  
  12. public void setVersion(Integer version) {  
  13. this.version = version;  
  14. }  
  15. // Person-specific fields and behavior here   
  16. public boolean equals(Object o) {   
  17. if (this == o) return true;   
  18. if (o == null || !(o instanceof Person)) return false;   
  19. Person other = (Person)o;   
  20. if (id == null) return false;   
  21. return id.equals(other.getId());   
  22. }  
  23. public int hashCode() {  
  24. if (id != null) {   
  25. return id.hashCode();   
  26. } else {  
  27. return super.hashCode();   
  28. }  
  29. }  
  30. }  

这个例子使用id作为equals() 方法判断等价的标准以及hashCode()返回哈希值的来源。这就简单了许多。但是,要让它正常工作,我们需要两样东西。首先,我们需要保证每个对象在被保存之前都有一个id值。在这个例子里,当id变量被声明的时候,它就被指派了一个值。其次,我们需要一种判断这个对象是新生成的还是之前保存过的的手段。在我们最早的例子中,Hibernate检查id字段是否为空来判断对象是否时新生成的。既然我们的对象id永远不为空,这个方法显然不再有效。为了解决这个问题,我们可以很容易的配置Hibernate,让它检查version字段,而不是id字段是否为空。version字段是一个更为恰当的用来判断你的对象是否被保存过的指示器。

下面是我们改进过的Person类的Hibernate映射文件。

  1. <?XML version="1.0"?>  
  2. <hibernate-mapping package="my.package">  
  3. <class name="Person" table="PERSON">  
  4. <id name="id" column="ID">  
  5. <generator class="assigned" />  
  6. </id>   
  7. <version name="version" column="VERSION" unsaved-value="null" />   
  8. <!-- Map Person-specific properties here. -->  
  9. </class>  
  10. </hibernate-mapping> 

注意,id下面的generator标签包含了属性class="assigned".这个属性告诉Hibernate我们不是让数据库指派id值而是在我们的代码里面指派id值。Hibernate会简单地认为即使是新的,没有经过保存的对象也有id值。我们也给version标签新增了一个unsaved-value="null"的属性。这个属性告诉Hibernate应该把version值而不是id值为null作为对象是新创建而成的指示器。我们也可以简单的告诉Hibernate把负值作为对象未经保存的指示器,如果你喜欢把version字段的类型设置为int而不是Integer,这将是很有用的。以上介绍Hibernate检查id字段。