在前一篇文章
《使用Hibernate來實現持久對象》中,介紹了Hibernate的基本概念,然后用實例演示了怎么在Web應用中使用Hibernate來封裝持久數據對象。然而在現實的項目中,我們往往需要操作多個數據表,并且多個表之間往往存在復雜的關系,在本文,將介紹怎么在Hibernate中描述多個表的映射關系,并且演示怎么操作關系復雜的持久對象。
本文的全部代碼在這里
下載案例介紹
在第一篇文章中,我們對一個表進行了簡單的封裝。在這篇文章中,我們討論更加復雜的情況。
在這個例子中,將考慮到表之間的一對一、一對多、多對多的情況。如圖1所示。
圖1 實體之間的映射關系
在上面的數據模型圖中,Student是所有表的核心,它和Classes表是一對多的關系,和Course表是多對多的關系(通過Student_Course_Link表來鏈接),和Address表是一對一的關系。
通過分析,我們可以把上面的數據模型轉換成如下的Java持久對象,如圖2所示。
圖2 持久對象之間的關系
可以看出,數據模型圖和Java持久對象的類圖有非常大的相似性,但是不完全相同。比如Classes表和Student表是一對多的關系;在類圖中,兩者仍然是一對多的關系,但是在Classes類中添加了一個student屬性,屬性的類型是java.util.Set,它表示Classes對象中包含的所有Student對象。
創(chuàng)建Hibernate持久對象
已經對數據模型經過了分析,現在就可以創(chuàng)建持久對象了。持久對象之間的關系由圖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"/
使用映射關系
下面我們開發(fā)一個簡單的實例來測試這個映射。持久對象使用最頻繁的操作是增加數據、查詢數據、刪除數據、更新數據。對于更新數據的操作的情況,多個表的操作和單個表沒有兩樣,在這里不舉例了。
添加數據到數據庫
我們在這里測試前三種操作,首先來看添加數據到數據庫的情況,如例程4所示。
例程4 測試持久對象之間的映射關系之添加數據(MapTestBean.java部分代碼)
/**
*在數據庫中添加數據
*/
public void addData(String studentId,String classesId,String coursesId)
throws HibernateException {
try
{
/**
*以下的代碼添加了一個Student,同時為Student指定了
*Address、Courses和Classses。
*/
beginTransaction();
//創(chuàng)建一個Student對象 。
Student student = new Student();
student.setName("hellking2");
student.setId(studentId);
//創(chuàng)建一個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);
//創(chuàng)建一個course對象。
Course course=new Course ();
course.setId(coursesId);
course.setName("computer_jsp");
//設置course和student對象之間的關系。
course.setStudents(set);
//創(chuàng)建一個classes對象。
Classes cl=new Classes();
cl.setId(classesId);
cl.setName("engine power");
//設置某個classes對象包含的students對象。
cl.setStudents(set);
//由于是雙向的關系,student對象也需要設置一次。
student.setClasses(cl);
//保存創(chuàng)建的對象到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");
這種設置屬性的方式和普通的類沒有什么區(qū)別,設置完所有的屬性后,就設置持久對象之間的關系,如:
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()方法創(chuàng)建一個Hibernate會話對象,并且開始一個新Hibernate事務;然后通過session.load()方法獲得給定ID的Student對象,如:
Student st=(Student)session.load(Student.class,id);
最后調用student.getXXX()方法返回指定的對象。
刪除數據
在表的關系比較復雜時,要刪除數據,往往存在級聯(lián)刪除的情況,由于級聯(lián)刪除的情況比較復雜,在這里就不舉例了。假設我們要刪除和某個給定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);
}
同樣,在執(zhí)行刪除前,首先使用beginTransaction()方法創(chuàng)建一個新Hibernate會話和一個新Hibernate事務,然后把要刪除的對象Load進來,接下來調用session.delete()方法來刪除指定對象。
如果要刪除的是集合中的對象,那么可以通過一個迭代來逐個刪除,如例程6中刪除courses的方法。
測試
在這里提供了在JSP中調用MapTestBean進行測試的程序,具體代碼見maptest.jsp文件。在進行測試前,請確保連接數據庫的配置完好,并且每個持久對象的配置都沒有錯誤。如果配置出現困難,請參考本文的源代碼。
行動起來
經過兩篇文章由淺入深的學習,希望能夠起到拋磚引玉的作用,相信讀者對Hibernate的認識已經有一個整體的把握。Hibernate由于它的易用性和良好的移植性等特點,逐漸在企業(yè)級應用開發(fā)中廣泛使用。Hibernate官方網站提供了非常好的使用手冊,您可以參考它。如果您并非精通JDBC并且不想學習它,不妨考慮使用Hibernate;如果您在使用實體Bean之類的持久框架遇到困難,也許Hibernate可以助你一臂之力!