|
在前一篇文章《使用Hibernate來(lái)實(shí)現(xiàn)持久對(duì)象》中,介紹了Hibernate的基本概念,然后用實(shí)例演示了怎么在Web應(yīng)用中使用Hibernate來(lái)封裝持久數(shù)據(jù)對(duì)象。然而在現(xiàn)實(shí)的項(xiàng)目中,我們往往需要操作多個(gè)數(shù)據(jù)表,并且多個(gè)表之間往往存在復(fù)雜的關(guān)系,在本文,將介紹怎么在Hibernate中描述多個(gè)表的映射關(guān)系,并且演示怎么操作關(guān)系復(fù)雜的持久對(duì)象。
本文的全部代碼在這里下載
案例介紹
在第一篇文章中,我們對(duì)一個(gè)表進(jìn)行了簡(jiǎn)單的封裝。在這篇文章中,我們討論更加復(fù)雜的情況。
在這個(gè)例子中,將考慮到表之間的一對(duì)一、一對(duì)多、多對(duì)多的情況。如圖1所示。
 圖1 實(shí)體之間的映射關(guān)系
在上面的數(shù)據(jù)模型圖中,Student是所有表的核心,它和Classes表是一對(duì)多的關(guān)系,和Course表是多對(duì)多的關(guān)系(通過(guò)Student_Course_Link表來(lái)鏈接),和Address表是一對(duì)一的關(guān)系。
通過(guò)分析,我們可以把上面的數(shù)據(jù)模型轉(zhuǎn)換成如下的Java持久對(duì)象,如圖2所示。
 圖2 持久對(duì)象之間的關(guān)系
可以看出,數(shù)據(jù)模型圖和Java持久對(duì)象的類(lèi)圖有非常大的相似性,但是不完全相同。比如Classes表和Student表是一對(duì)多的關(guān)系;在類(lèi)圖中,兩者仍然是一對(duì)多的關(guān)系,但是在Classes類(lèi)中添加了一個(gè)student屬性,屬性的類(lèi)型是java.util.Set,它表示Classes對(duì)象中包含的所有Student對(duì)象。
創(chuàng)建Hibernate持久對(duì)象
已經(jīng)對(duì)數(shù)據(jù)模型經(jīng)過(guò)了分析,現(xiàn)在就可以創(chuàng)建持久對(duì)象了。持久對(duì)象之間的關(guān)系由圖2所示的類(lèi)圖指定。
我們首先來(lái)看Student類(lèi),它是這個(gè)關(guān)系映射的核心,代碼如例程1所示。
例程1 Student持久對(duì)象(Student.java)
package com.hellking.study.hibernate;
import java.util.Set; /** *在hibernate中代表了Students表的類(lèi)。 */ public class Student { /**屬性,和students表中的字段對(duì)應(yīng)**/ private String id; private String name; /**和其它類(lèi)之間的映射關(guān)系**/ private Set courses; private Classes classes; private Address address; /**屬性的訪問(wèn)方法,必須是公共的方法**/ 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; } /**操作和其它對(duì)象之間的關(guān)系**/ 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類(lèi)中,由于Students表和Classes的表是多對(duì)一的關(guān)系,故它包含了一個(gè)類(lèi)型為Classes的classes屬性,它的實(shí)際意義是一個(gè)學(xué)生可以有一個(gè)班級(jí);Students表和Address的表是一對(duì)一的關(guān)系,同樣也包含了一個(gè)類(lèi)型為Address的address屬性,它的實(shí)際意義是一個(gè)學(xué)生有一個(gè)地址;Students表和Course是多對(duì)多的關(guān)系,故它包含了一個(gè)類(lèi)型為java.util.Set的course屬性,它的實(shí)際意義是一個(gè)學(xué)生可以學(xué)習(xí)多門(mén)課程,同樣,某個(gè)課程可以由多個(gè)學(xué)生來(lái)選修。
Classes對(duì)象和Student對(duì)象是一對(duì)多的關(guān)系。Classes對(duì)象包含一個(gè)類(lèi)型為java.util.Set的students屬性,它的代碼如例程2所示。
例程2 Classes持久對(duì)象(Classes.java)
package com.hellking.study.hibernate;
import java.util.Set; /** *在hibernate中代表了Classes表的類(lèi)。 */ public class Classes { /**屬性,和classes表的字段一致**/ private String id; private String name; /**和其它類(lèi)之間的映射關(guān)系**/ private Set students; /**屬性的訪問(wèn)方法,必須是公共的方法**/ 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; } /**操作和其它對(duì)象之間的關(guān)系**/ public void setStudents(Set stud) { this.students=stud; } public Set getStudents() { return this.students; } }
Course持久對(duì)象在前一篇文章已經(jīng)介紹,在這里就不再列舉。Address持久對(duì)象比較簡(jiǎn)單,除了表字段定義的屬性外,沒(méi)有引入其它的屬性,請(qǐng)參考本文的代碼。
描述對(duì)象之間的關(guān)系
現(xiàn)在我們已經(jīng)編寫(xiě)好了持久對(duì)象,下面的任務(wù)就是描述它們之間的關(guān)系。首先我們看Student持久對(duì)象的描述,如例程3所示。
例程3 Student持久對(duì)象的描述(Student.hbm.xml)
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN" "http://hibernate./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多對(duì)多的關(guān)系--> <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之間多對(duì)一的關(guān)系--> <many-to-one name="classes" class="com.hellking.study.hibernate.Classes" cascade="none" outer-join="auto" update="true" insert="true" column="ClassesId" /> <!-- 描述Student和Address之間一對(duì)一的關(guān)系--> <one-to-one name="address" class="com.hellking.study.hibernate.Address" cascade="none" outer-join="auto" constrained="false" /> </class> </hibernate-mapping>
在Student.hbm.xml描述符中,共描述了三種關(guān)系。第一種是Student和Address之間一對(duì)一的關(guān)系,它是最簡(jiǎn)單的關(guān)系,使用:
<one-to-one name="" class="">
來(lái)描述,這里的name表示的是Student對(duì)象中名稱(chēng)為address的屬性;class表示的是address屬性的類(lèi)型:com.hellking.study.hibernate.Address。
接下來(lái)看Student和Classes之間多對(duì)一的關(guān)系,使用:
<many-to-one name="classes" class="com.hellking.study.hibernate.Classes" column="ClassesId" … />
來(lái)描述。同樣,name表示的是Student對(duì)象中名稱(chēng)為classes的屬性;class表示的是classes屬性的類(lèi)型,column表示Student表引用Classes表使用的外部鍵名稱(chēng)。對(duì)應(yīng)的,在Classes類(lèi)中也引用了Student類(lèi),它使用了以下的描述符來(lái)描述這個(gè)關(guān)系:
<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之間多對(duì)多關(guān)系時(shí),使用了以下的方法:
<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>
在映射多對(duì)多關(guān)系時(shí),需要另外使用一個(gè)鏈接表,這個(gè)表的名字由table屬性指定,鏈接表包含了兩個(gè)字段:CourseId和StudentId。以下的描述:
<key column="StudentId">
指定了Student對(duì)象在Student_Course_Link鏈接表中的外部鍵。對(duì)應(yīng)的,在Course持久對(duì)象使用了以下的描述符來(lái)描述這個(gè)關(guān)系:
<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>
由于其它持久對(duì)象的描述基本一樣,在這里就不一一列舉了,請(qǐng)參考本文的源代碼。最后別忘了在hibernate.cfg.xml里增加這幾個(gè)對(duì)象的描述。
<!-- Mapping files --> <mapping resource="Address.hbm.xml"/> <mapping resource="Student.hbm.xml"/> <mapping resource="Classes.hbm.xml"/> <mapping resource="Course.hbm.xml"/
使用映射關(guān)系
下面我們開(kāi)發(fā)一個(gè)簡(jiǎn)單的實(shí)例來(lái)測(cè)試這個(gè)映射。持久對(duì)象使用最頻繁的操作是增加數(shù)據(jù)、查詢(xún)數(shù)據(jù)、刪除數(shù)據(jù)、更新數(shù)據(jù)。對(duì)于更新數(shù)據(jù)的操作的情況,多個(gè)表的操作和單個(gè)表沒(méi)有兩樣,在這里不舉例了。
添加數(shù)據(jù)到數(shù)據(jù)庫(kù)
我們?cè)谶@里測(cè)試前三種操作,首先來(lái)看添加數(shù)據(jù)到數(shù)據(jù)庫(kù)的情況,如例程4所示。
例程4 測(cè)試持久對(duì)象之間的映射關(guān)系之添加數(shù)據(jù)(MapTestBean.java部分代碼)
/** *在數(shù)據(jù)庫(kù)中添加數(shù)據(jù) */ public void addData(String studentId,String classesId,String coursesId) throws HibernateException { try { /** *以下的代碼添加了一個(gè)Student,同時(shí)為Student指定了 *Address、Courses和Classses。 */ beginTransaction(); //創(chuàng)建一個(gè)Student對(duì)象 。 Student student = new Student(); student.setName("hellking2"); student.setId(studentId); //創(chuàng)建一個(gè)Address對(duì)象。 Address addr=new Address(); addr.setCity("beijing"); addr.setState("bj"); addr.setStreet("tsinghua"); addr.setZip("100083"); addr.setId(student.getId()); //設(shè)置Student和address的關(guān)系。 student.setAddress(addr); Set set=new HashSet(); set.add(student); //創(chuàng)建一個(gè)course對(duì)象。 Course course=new Course (); course.setId(coursesId); course.setName("computer_jsp"); //設(shè)置course和student對(duì)象之間的關(guān)系。 course.setStudents(set); //創(chuàng)建一個(gè)classes對(duì)象。 Classes cl=new Classes(); cl.setId(classesId); cl.setName("engine power"); //設(shè)置某個(gè)classes對(duì)象包含的students對(duì)象。 cl.setStudents(set); //由于是雙向的關(guān)系,student對(duì)象也需要設(shè)置一次。 student.setClasses(cl); //保存創(chuàng)建的對(duì)象到session中。 session.save(cl); session.save(course); session.save(student); session.save(addr); //提交事務(wù),使更改生效。 endTransaction(true); } catch(HibernateException e) { System.out.println("在添加數(shù)據(jù)時(shí)出錯(cuò)!"); e.printStackTrace(); throw e; } }
在例程4中,添加數(shù)據(jù)到數(shù)據(jù)庫(kù)之前,首先設(shè)置持久對(duì)象的各個(gè)屬性,如:
student.setName("hellking2");
這種設(shè)置屬性的方式和普通的類(lèi)沒(méi)有什么區(qū)別,設(shè)置完所有的屬性后,就設(shè)置持久對(duì)象之間的關(guān)系,如:
student.setAddress(addr);
如果存在對(duì)象之間的多重關(guān)系,那么可能需要把對(duì)象保存在Set集合中,然后再進(jìn)行設(shè)置,如:
Set set=new HashSet(); set.add(student); course.setStudents(set);
當(dāng)設(shè)置完所有的屬性和對(duì)象關(guān)系之后,就可以調(diào)用:
session.save(persistentObject);
方法把持久對(duì)象保存到Hibernate會(huì)話(huà)中。最后,調(diào)用endTransaction來(lái)提交事務(wù),并且關(guān)閉Hibernate會(huì)話(huà)。
數(shù)據(jù)查詢(xún)
在復(fù)雜的實(shí)體對(duì)象映射中,往往查詢(xún)也比較復(fù)雜。作為演示,我們?cè)谶@里也提供了幾個(gè)查詢(xún)方法,如例程5所示。
例程5 測(cè)試持久對(duì)象之間的映射關(guān)系之查詢(xún)數(shù)據(jù)(MapTestBean.java部分代碼)
/** *獲得某個(gè)給定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; } /** *獲得某個(gè)給定studentid的Student的所有課程 */ public Set getCourses(String id)throws HibernateException { beginTransaction(); Student st=(Student)session.load(Student.class,id); endTransaction(false); return st.getCourses(); } /** *測(cè)試獲得某個(gè)給定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(); }
這里提供了三種查詢(xún)方法,分別是:
查詢(xún)給定id的Student的Address信息; 查詢(xún)給定id的Student的所有Courses信息; 查詢(xún)給定id的Student所屬的Classes信息。
在查詢(xún)時(shí),首先使用beginTransaction()方法創(chuàng)建一個(gè)Hibernate會(huì)話(huà)對(duì)象,并且開(kāi)始一個(gè)新Hibernate事務(wù);然后通過(guò)session.load()方法獲得給定ID的Student對(duì)象,如:
Student st=(Student)session.load(Student.class,id);
最后調(diào)用student.getXXX()方法返回指定的對(duì)象。
刪除數(shù)據(jù)
在表的關(guān)系比較復(fù)雜時(shí),要?jiǎng)h除數(shù)據(jù),往往存在級(jí)聯(lián)刪除的情況,由于級(jí)聯(lián)刪除的情況比較復(fù)雜,在這里就不舉例了。假設(shè)我們要?jiǎng)h除和某個(gè)給定id的student對(duì)象的所有相關(guān)的記錄,就可以使用例程6所示的方法。
例程6 測(cè)試持久對(duì)象之間的映射關(guān)系之刪除數(shù)據(jù)(MapTestBean.java部分代碼)
/** *刪除和某個(gè)學(xué)生相關(guān)的所有信息 *(這里只是測(cè)試,我們暫且不說(shuō)這種操作的意義何在)。 */ 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()); /** *逐個(gè)刪除course。 */ for(Iterator it=st.getCourses().iterator();it.hasNext();) { Course c=(Course)it.next(); session.delete(c); } //最后,刪除student對(duì)象。 session.delete(st); endTransaction(true); }
同樣,在執(zhí)行刪除前,首先使用beginTransaction()方法創(chuàng)建一個(gè)新Hibernate會(huì)話(huà)和一個(gè)新Hibernate事務(wù),然后把要?jiǎng)h除的對(duì)象Load進(jìn)來(lái),接下來(lái)調(diào)用session.delete()方法來(lái)刪除指定對(duì)象。
如果要?jiǎng)h除的是集合中的對(duì)象,那么可以通過(guò)一個(gè)迭代來(lái)逐個(gè)刪除,如例程6中刪除courses的方法。
測(cè)試
在這里提供了在JSP中調(diào)用MapTestBean進(jìn)行測(cè)試的程序,具體代碼見(jiàn)maptest.jsp文件。在進(jìn)行測(cè)試前,請(qǐng)確保連接數(shù)據(jù)庫(kù)的配置完好,并且每個(gè)持久對(duì)象的配置都沒(méi)有錯(cuò)誤。如果配置出現(xiàn)困難,請(qǐng)參考本文的源代碼。
行動(dòng)起來(lái)
經(jīng)過(guò)兩篇文章由淺入深的學(xué)習(xí),希望能夠起到拋磚引玉的作用,相信讀者對(duì)Hibernate的認(rèn)識(shí)已經(jīng)有一個(gè)整體的把握。Hibernate由于它的易用性和良好的移植性等特點(diǎn),逐漸在企業(yè)級(jí)應(yīng)用開(kāi)發(fā)中廣泛使用。Hibernate官方網(wǎng)站提供了非常好的使用手冊(cè),您可以參考它。如果您并非精通JDBC并且不想學(xué)習(xí)它,不妨考慮使用Hibernate;如果您在使用實(shí)體Bean之類(lèi)的持久框架遇到困難,也許Hibernate可以助你一臂之力!
|