跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

在 Hibernate 中实现复杂的数据映射

陈亚强 (cyqcims@mail.tsinghua.edu.cn), 高级软件工程师
陈亚强:北京华园天一科技有限公司高级软件工程师,擅长J2EE技术,曾参与多个J2EE项目的设计和开发,对Web服务有很大的兴趣并且有一定的项目经验。热爱学习,喜欢新技术,曾参与多本图书的写作。好交朋友,您可以通过 cyqcims@mail.tsinghua.edu.cn和他联系。

简介: 在前一篇文章《使用 Hibernate 来操作持久对象》中,介绍了 Hibernate 的基本概念,然后用实例演示了怎么在 Web 应用中使用 Hibernate 来封装持久数据对象。然而在现实的项目中,我们往往需要操作多个数据表,并且多个表之间往往存在复杂的关系,在本文,将介绍怎么在 Hibernate 中描述多个表的映射关系,并且演示怎么操作关系复杂的持久对象。

发布日期: 2003 年 10 月 13 日
级别: 初级
访问情况 : 3621 次浏览
评论: 


阅读本文前您需要以下的知识和工具:

本文的参考资料见 参考资料

本文的全部代码在这里 下载

案例介绍

在第一篇文章中,我们对一个表进行了简单的封装。在这篇文章中,我们讨论更加复杂的情况。

在这个例子中,将考虑到表之间的一对一、一对多、多对多的情况。如图 1 所示。


图 1 实体之间的映射关系
图 1 实体之间的映射关系

在上面的数据模型图中,Student 是所有表的核心,它和 Classes 表是一对多的关系,和 Course 表是多对多的关系(通过 Student_Course_Link 表来链接),和 Address 表是一对一的关系。

通过分析,我们可以把上面的数据模型转换成如下的 Java 持久对象,如图 2 所示。


图 2 持久对象之间的关系
图 2 持久对象之间的关系

可以看出,数据模型图和 Java 持久对象的类图有非常大的相似性,但是不完全相同。比如 Classes 表和 Student 表是一对多的关系;在类图中,两者仍然是一对多的关系,但是在 Classes 类中添加了一个 student 属性,属性的类型是 java.util.Set,它表示 Classes 对象中包含的所有 Student 对象。


创建 Hibernate 持久对象

已经对数据模型经过了分析,现在就可以创建持久对象了。持久对象之间的关系由图 2 所示的类图指定。

我们首先来看 Student 类,它是这个关系映射的核心,代码如例程 1 所示。


例程 1 Student 持久对象(Student.java)
 package com.hellking.study.hibernate; 
 import java.util.Set; 
 /** 
 * 在 hibernate 中代表了 Students 表的类。
 */ 
 public class Student 
 { 
   /** 属性,和 students 表中的字段对应 **/ 
   private String id; 
   private String name; 
   /** 和其它类之间的映射关系 **/ 
   private Set courses; 
   private Classes classes; 
   private Address address; 
   
   	 /** 属性的访问方法 , 必须是公共的方法 **/ 
   	 public void setId(String string) { 
		 id = string; 
	 } 
	
	 public String getId() { 
		 return id; 
	 } 
	
	 public void setName(String name) 
	 { 
		 this.name=name; 
	 } 
	 public String getName() 
	 { 
		 return this.name; 
	 } 
	
	 /** 操作和其它对象之间的关系 **/ 
	 public void setCourses(Set co) 
	 { 
		 this.courses=co; 
	 } 
	 public Set getCourses() 
	 { 
		 return this.courses; 
	 } 
	 public void setAddress(Address ad) 
	 { 
		 this.address=address; 
	 } 
	 public Address getAddress() 
	 { 
		 return this.address; 
	 } 
	 public void setClasses(Classes c) 
	 { 
		 this.classes=c; 
	 } 
	 public Classes getClasses() 
	 { 
		 return this.classes; 
	 } 		
 } 

在 Student 类中,由于 Students 表和 Classes 的表是多对一的关系,故它包含了一个类型为 Classes 的 classes 属性,它的实际意义是一个学生可以有一个班级;Students 表和 Address 的表是一对一的关系,同样也包含了一个类型为 Address 的 address 属性,它的实际意义是一个学生有一个地址;Students 表和 Course 是多对多的关系,故它包含了一个类型为 java.util.Set 的 course 属性,它的实际意义是一个学生可以学习多门课程,同样,某个课程可以由多个学生来选修。

Classes 对象和 Student 对象是一对多的关系。Classes 对象包含一个类型为 java.util.Set 的 students 属性,它的代码如例程 2 所示。


例程 2 Classes 持久对象(Classes.java)
 package com.hellking.study.hibernate; 
 import java.util.Set; 
 /** 
 * 在 hibernate 中代表了 Classes 表的类。
 */ 
 public class Classes 
 { 
   /** 属性,和 classes 表的字段一致 **/ 
   private String id;  
   private String name; 
   /** 和其它类之间的映射关系 **/ 
   private Set students; 
   
   /** 属性的访问方法 , 必须是公共的方法 **/ 
   public void setId(String string) { 
		 id = string; 
	 } 
	
	 public String getId() { 
		 return id; 
	 } 
	
	 public void setName(String name) 
	 { 
		 this.name=name; 
	 } 
	 public String getName() 
	 { 
		 return this.name; 
	 } 
	
	 /** 操作和其它对象之间的关系 **/ 
	 public void setStudents(Set stud) 
	 { 
		 this.students=stud; 
	 } 
	 public Set getStudents() 
	 { 
		 return this.students; 
	 } 
 } 

Course 持久对象在前一篇文章已经介绍,在这里就不再列举。Address 持久对象比较简单,除了表字段定义的属性外,没有引入其它的属性,请参考本文的代码。


描述对象之间的关系

现在我们已经编写好了持久对象,下面的任务就是描述它们之间的关系。首先我们看 Student 持久对象的描述,如例程 3 所示。


例程 3 Student 持久对象的描述(Student.hbm.xml)
 <?xml version="1.0"?> 
 <!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 2.0//EN" 
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> 
 <hibernate-mapping> 
    <class 
        name="com.hellking.study.hibernate.Student"
        table="Students"
        dynamic-update="false"
    > 
   <!-- 描述 ID 字段 --> 
        <id 
            name="id"
            column="StudentId"
            type="string"
            unsaved-value="any"
        > 
            <generator class="assigned"/> 
        </id> 
     <!-- 属性 --> 
         <property 
            name="name"
            type="string"
            update="true"
            insert="true"
            column="Name"
        /> 
 <!-- 描述 Student 和 Course 多对多的关系 --> 
         <set 
            name="courses"
            table="Student_Course_Link"
            lazy="false"
            inverse="false"
            cascade="all"
            sort="unsorted"
        > 
              <key 
                  column="StudentId"
              /> 
              <many-to-many 
                  class="com.hellking.study.hibernate.Course"
                  column="CourseId"
                  outer-join="auto"
              /> 
        </set> 
 <!-- 描述 Student 和 Classes 之间多对一的关系 --> 
          <many-to-one 
            name="classes"
            class="com.hellking.study.hibernate.Classes"
            cascade="none"
            outer-join="auto"
            update="true"
            insert="true"
            column="ClassesId"
        /> 
        
 <!-- 描述 Student 和 Address 之间一对一的关系 -->        
 <one-to-one 
            name="address"
            class="com.hellking.study.hibernate.Address"
            cascade="none"
            outer-join="auto"
            constrained="false"
        />       
    </class> 
 </hibernate-mapping> 

在 Student.hbm.xml 描述符中,共描述了三种关系。第一种是 Student 和 Address 之间一对一的关系,它是最简单的关系,使用:

 <one-to-one name="" class=""> 

来描述,这里的 name 表示的是 Student 对象中名称为 address 的属性;class 表示的是 address 属性的类型:com.hellking.study.hibernate.Address。

接下来看 Student 和 Classes 之间多对一的关系,使用:

<many-to-one   name="classes" class="com.hellking.study.hibernate.Classes"
column="ClassesId" …   /> 

来描述。同样,name 表示的是 Student 对象中名称为 classes 的属性;class 表示的是 classes 属性的类型,column 表示 Student 表引用 Classes 表使用的外部键名称。对应的,在 Classes 类中也引用了 Student 类,它使用了以下的描述符来描述这个关系:

        <set 
            name="students"
            table="Students"
            lazy="false"
            inverse="false"
            cascade="all"
            sort="unsorted"
        > 
              <key 
                  column="ClassesId"
              /> 
              <one-to-many 
                  class="com.hellking.study.hibernate.Student"
              /> 
        </set> 
       

在描述 Student 和 Course 之间多对多关系时,使用了以下的方法:

        <set 
            name="courses"
            table="Student_Course_Link"
            lazy="false"
            inverse="false"
            cascade="all"
            sort="unsorted"
        > 
              <key 
                  column="StudentId"
              /> 
              <many-to-many 
                  class="com.hellking.study.hibernate.Course"
                  column="CourseId"
                  outer-join="auto"
              /> 
        </set> 
       

在映射多对多关系时,需要另外使用一个链接表,这个表的名字由 table 属性指定,链接表包含了两个字段:CourseId 和 StudentId。以下的描述:

 <key column="StudentId"> 

指定了 Student 对象在 Student_Course_Link 链接表中的外部键。对应的,在 Course 持久对象使用了以下的描述符来描述这个关系:

        <set 
            name="students"
            table="Student_Course_Link"
            lazy="false"
            inverse="false"
            cascade="all"
            sort="unsorted"
        > 
              <key 
                  column="CourseId"
              /> 
              <many-to-many 
                  class="com.hellking.study.hibernate.Student"
                  column="StudentId"
                  outer-join="auto"
              /> 
        </set> 
       

由于其它持久对象的描述基本一样,在这里就不一一列举了,请参考本文的源代码。 最后别忘了在 hibernate.cfg.xml 里增加这几个对象的描述。

        <!-- Mapping files --> 
        <mapping resource="Address.hbm.xml"/> 
        <mapping resource="Student.hbm.xml"/> 
        <mapping resource="Classes.hbm.xml"/> 
        <mapping resource="Course.hbm.xml"/ 
       


使用映射关系

下面我们开发一个简单的实例来测试这个映射。持久对象使用最频繁的操作是增加数据、查询数据、删除数据、更新数据。对于更新数据的操作的情况,多个表的操作和单个表没有两样,在这里不举例了。

添加数据到数据库

我们在这里测试前三种操作,首先来看添加数据到数据库的情况,如例程 4 所示。


例程 4 测试持久对象之间的映射关系之添加数据(MapTestBean.java 部分代码)
    	 /** 
	 * 在数据库中添加数据
	 */ 
	 public void addData(String studentId,String classesId,String coursesId) 
        throws HibernateException { 
        try 
        { 
          /** 
           * 以下的代码添加了一个 Student,同时为 Student 指定了
           *Address、Courses 和 Classses。
           */ 
          beginTransaction();  
          // 创建一个 Student 对象 。       
          Student student = new Student(); 
          student.setName("hellking2"); 
          student.setId(studentId); 
          
          // 创建一个 Address 对象。
          Address addr=new Address(); 
          addr.setCity("beijing"); 
          addr.setState("bj"); 
          addr.setStreet("tsinghua"); 
          addr.setZip("100083"); 
          addr.setId(student.getId());        
			 // 设置 Student 和 address 的关系。
          student.setAddress(addr);       
         
         Set set=new HashSet(); 
         set.add(student); 
         // 创建一个 course 对象。
         Course course=new  Course  (); 
         course.setId(coursesId); 
         course.setName("computer_jsp"); 
         // 设置 course 和 student 对象之间的关系。
         course.setStudents(set); 
       
         // 创建一个 classes 对象。
         Classes cl=new Classes(); 
         cl.setId(classesId); 
         cl.setName("engine power"); 
         // 设置某个 classes 对象包含的 students 对象。
         cl.setStudents(set); 
        // 由于是双向的关系,student 对象也需要设置一次。
         student.setClasses(cl);        
         
         // 保存创建的对象到 session 中。
         session.save(cl); 
         session.save(course); 
         session.save(student); 
         session.save(addr); 
         // 提交事务,使更改生效。
         endTransaction(true); 
       } 
       catch(HibernateException e) 
       {         
           System.out.println("在添加数据时出错!"); 
           e.printStackTrace(); 
       	   throw e; 
       } 
    } 
   

在例程 4 中,添加数据到数据库之前,首先设置持久对象的各个属性,如:

 student.setName("hellking2"); 

这种设置属性的方式和普通的类没有什么区别,设置完所有的属性后,就设置持久对象之间的关系,如:

 student.setAddress(addr); 

如果存在对象之间的多重关系,那么可能需要把对象保存在 Set 集合中,然后再进行设置,如:

 Set set=new HashSet(); 
 set.add(student); 
 course.setStudents(set); 

当设置完所有的属性和对象关系之后,就可以调用:

 session.save(persistentObject); 

方法把持久对象保存到 Hibernate 会话中。最后,调用 endTransaction 来提交事务,并且关闭 Hibernate 会话。

数据查询

在复杂的实体对象映射中,往往查询也比较复杂。作为演示,我们在这里也提供了几个查询方法,如例程 5 所示。


例程 5 测试持久对象之间的映射关系之查询数据(MapTestBean.java 部分代码)
    /** 
     * 获得某个给定 studentid 的 Student 的地址信息
     */ 
    public Address getAddress(String id) throws HibernateException 
    {    	
    	    beginTransaction();    	
	    	 Student st=(Student)session.load(Student.class,id);    
	    	 Address addr=(Address)session.load(Address.class,st.getId()); 
	    	 endTransaction(false); 	
			 return addr; 
	   
    } 
    
    /** 
     * 获得某个给定 studentid 的 Student 的所有课程
     */ 
    public Set getCourses(String id)throws HibernateException 
    { 
    	 beginTransaction();    	
    	 Student st=(Student)session.load(Student.class,id); 
     	 endTransaction(false); 	   
    	 return st.getCourses(); 
    } 
    
    /** 
     * 测试获得某个给定 studentid 的 Student 所属的 Classes 
     */ 
    public Classes getClasses(String id)throws HibernateException 
    {    	
    	 beginTransaction();    	
    	 Student st=(Student)session.load(Student.class,id); 
    	 System.out.println(st.getClasses().getId());  
    	 endTransaction(false); 	
    	 return st.getClasses(); 
    } 
   

这里提供了三种查询方法,分别是:

  • 查询给定 id 的 Student 的 Address 信息;
  • 查询给定 id 的 Student 的所有 Courses 信息;
  • 查询给定 id 的 Student 所属的 Classes 信息。

在查询时,首先使用 beginTransaction() 方法创建一个 Hibernate 会话对象,并且开始一个新 Hibernate 事务;然后通过 session.load() 方法获得给定 ID 的 Student 对象,如:

 Student st=(Student)session.load(Student.class,id); 

最后调用 student.getXXX() 方法返回指定的对象。

删除数据

在表的关系比较复杂时,要删除数据,往往存在级联删除的情况,由于级联删除的情况比较复杂,在这里就不举例了。假设我们要删除和某个给定 id 的 student 对象的所有相关的记录,就可以使用例程 6 所示的方法。

例程 6 测试持久对象之间的映射关系之删除数据(MapTestBean.java 部分代码)

    /** 
     * 删除和某个学生相关的所有信息
     *(这里只是测试,我们暂且不说这种操作的意义何在)。
     */ 
    public void delteStudent(String id)throws HibernateException 
    { 
    	 beginTransaction();    	
    	 Student st=(Student)session.load(Student.class,id);     	
    	 Address addr=(Address)session.load(Address.class,st.getId()); 
    	 // 删除 address 信息。
    	 session.delete(addr); 
    	 // 删除 classes 信息。
    	 session.delete(st.getClasses()); 
    	 /** 
    	 * 逐个删除 course。
    	 */ 
        for(Iterator it=st.getCourses().iterator();it.hasNext();) 
        { 
        	 Course c=(Course)it.next(); 
        	 session.delete(c); 
        } 
        // 最后,删除 student 对象。
    	 session.delete(st); 
    	 endTransaction(true); 	
    } 
   

同样,在执行删除前,首先使用 beginTransaction() 方法创建一个新 Hibernate 会话和一个新 Hibernate 事务,然后把要删除的对象 Load 进来,接下来调用 session.delete() 方法来删除指定对象。

如果要删除的是集合中的对象,那么可以通过一个迭代来逐个删除,如例程 6 中删除 courses 的方法。


测试

在这里提供了在 JSP 中调用 MapTestBean 进行测试的程序,具体代码见 maptest.jsp 文件。在进行测试前,请确保连接数据库的配置完好,并且每个持久对象的配置都没有错误。如果配置出现困难,请参考本文的源代码。


行动起来

经过两篇文章由浅入深的学习,希望能够起到抛砖引玉的作用,相信读者对 Hibernate 的认识已经有一个整体的把握。Hibernate 由于它的易用性和良好的移植性等特点,逐渐在企业级应用开发中广泛使用。Hibernate 官方网站提供了非常好的使用手册,您可以参考它。如果您并非精通 JDBC 并且不想学习它,不妨考虑使用 Hibernate;如果您在使用实体 Bean 之类的持久框架遇到困难,也许 Hibernate 可以助你一臂之力!


参考资料

本文的源代码在 这里下载。

http://www.apache.org下载 Tomcat。

Hibernate 的官方网站, http://hibernate.bluemars.net/,包含了 Hibernate 最新资料。

Hibernate 中文论坛,hibernate.fankai.com 包含了 Hibernate 较多的参考资料。 包含了 Hibernate 技术讨论网站, www.jdon.com

于 Hibernate、JDO、CMP 等技术的热烈讨论 1: http://www.jdon.com/jive/thread.jsp?forum=16&thread=6062&start=0&msRange=15

于 Hibernate、JDO、CMP 等技术的热烈讨论 2: http://www.theserverside.com/discussion/thread.jsp?thread_id=19732

Hibernate2 Reference Documentation,可以从 Hibernate 官方网站获得,非常好的参考资料。

Hibernate In Action,一本非常专业的 Hibernate 参考书,由 Hibernate 项目主要开发人员 Gavin King 等著,Manning 出版社出版。您可以从 http://www.theserverside.com获得本书的部分章节。

关于作者

陈亚强:北京华园天一科技有限公司高级软件工程师,擅长J2EE技术,曾参与多个J2EE项目的设计和开发,对Web服务有很大的兴趣并且有一定的项目经验。热爱学习,喜欢新技术,曾参与多本图书的写作。好交朋友,您可以通过 cyqcims@mail.tsinghua.edu.cn和他联系。

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=52968
ArticleTitle=在 Hibernate 中实现复杂的数据映射
publish-date=10132003
author1-email=cyqcims@mail.tsinghua.edu.cn
author1-email-cc=cyqcims@mail.tsinghua.edu.cn

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。