阅读本文前您需要以下的知识和工具:
- Tomcat 5.09,可以从 www.apache.org下载;
- Hibernate2.0 相关运行环境,可以从 http://hibernate.bluemars.net/下载;
- 至少安装了一个数据库服务器并且有相关的 JDBC 驱动程序。
本文的参考资料见 参考资料
本文的全部代码在这里 下载
在第一篇文章中,我们对一个表进行了简单的封装。在这篇文章中,我们讨论更加复杂的情况。
在这个例子中,将考虑到表之间的一对一、一对多、多对多的情况。如图 1 所示。
图 1 实体之间的映射关系
在上面的数据模型图中,Student 是所有表的核心,它和 Classes 表是一对多的关系,和 Course 表是多对多的关系(通过 Student_Course_Link 表来链接),和 Address 表是一对一的关系。
通过分析,我们可以把上面的数据模型转换成如下的 Java 持久对象,如图 2 所示。
图 2 持久对象之间的关系
可以看出,数据模型图和 Java 持久对象的类图有非常大的相似性,但是不完全相同。比如 Classes 表和 Student 表是一对多的关系;在类图中,两者仍然是一对多的关系,但是在 Classes 类中添加了一个 student 属性,属性的类型是 java.util.Set,它表示 Classes 对象中包含的所有 Student 对象。
已经对数据模型经过了分析,现在就可以创建持久对象了。持久对象之间的关系由图 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和他联系。