本文的全部代碼在這里下載
案例介紹
在第一篇文章中,我們對(duì)一個(gè)表進(jìn)行了簡(jiǎn)單的封裝。在這篇文章中,我們討論更加復(fù)雜的情況。
在這個(gè)例子中,將考慮到表之間的一對(duì)一、一對(duì)多、多對(duì)多的情況。如圖1所示。
在上面的數(shù)據(jù)模型圖中,Student是所有表的核心,它和Classes表是一對(duì)多的關(guān)系,和Course表是多對(duì)多的關(guān)系(通過Student_Course_Link表來(lái)鏈接),和Address表是一對(duì)一的關(guān)系。
通過分析,我們可以把上面的數(shù)據(jù)模型轉(zhuǎn)換成如下的Java持久對(duì)象,如圖2所示。
可以看出,數(shù)據(jù)模型圖和Java持久對(duì)象的類圖有非常大的相似性,但是不完全相同。比如Classes表和Student表是一對(duì)多的關(guān)系;在類圖中,兩者仍然是一對(duì)多的關(guān)系,但是在Classes類中添加了一個(gè)student屬性,屬性的類型是java.util.Set,它表示Classes對(duì)象中包含的所有Student對(duì)象。
創(chuàng)建Hibernate持久對(duì)象
已經(jīng)對(duì)數(shù)據(jù)模型經(jīng)過了分析,現(xiàn)在就可以創(chuàng)建持久對(duì)象了。持久對(duì)象之間的關(guān)系由圖2所示的類圖指定。
我們首先來(lái)看Student類,它是這個(gè)關(guān)系映射的核心,代碼如例程1所示。
例程1 Student持久對(duì)象(Student.java)
package com.hellking.study.hibernate;
import java.util.Set;
/**
?*在hibernate中代表了Students表的類。
?*/
public class Student
{
?? /**屬性,和students表中的字段對(duì)應(yīng)**/
?? private String id;
?? private String name;
?? /**和其它類之間的映射關(guān)系**/
?? 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;
?}
?
?/**操作和其它對(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類中,由于Students表和Classes的表是多對(duì)一的關(guān)系,故它包含了一個(gè)類型為Classes的classes屬性,它的實(shí)際意義是一個(gè)學(xué)生可以有一個(gè)班級(jí);Students表和Address的表是一對(duì)一的關(guān)系,同樣也包含了一個(gè)類型為Address的address屬性,它的實(shí)際意義是一個(gè)學(xué)生有一個(gè)地址;Students表和Course是多對(duì)多的關(guān)系,故它包含了一個(gè)類型為java.util.Set的course屬性,它的實(shí)際意義是一個(gè)學(xué)生可以學(xué)習(xí)多門課程,同樣,某個(gè)課程可以由多個(gè)學(xué)生來(lái)選修。
Classes對(duì)象和Student對(duì)象是一對(duì)多的關(guān)系。Classes對(duì)象包含一個(gè)類型為java.util.Set的students屬性,它的代碼如例程2所示。
例程2 Classes持久對(duì)象(Classes.java)
package com.hellking.study.hibernate;
import java.util.Set;
/**
?*在hibernate中代表了Classes表的類。
?*/
public class Classes
{
?? /**屬性,和classes表的字段一致**/
?? private String id;?
?? private String name;
?? /**和其它類之間的映射關(guān)系**/
?? 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;
?}
?
?/**操作和其它對(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)單,除了表字段定義的屬性外,沒有引入其它的屬性,請(qǐng)參考本文的代碼。
描述對(duì)象之間的關(guān)系
現(xiàn)在我們已經(jīng)編寫好了持久對(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.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多對(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ì)象中名稱為address的屬性;class表示的是address屬性的類型: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ì)象中名稱為classes的屬性;class表示的是classes屬性的類型,column表示Student表引用Classes表使用的外部鍵名稱。對(duì)應(yīng)的,在Classes類中也引用了Student類,它使用了以下的描述符來(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)系
下面我們開發(fā)一個(gè)簡(jiǎn)單的實(shí)例來(lái)測(cè)試這個(gè)映射。持久對(duì)象使用最頻繁的操作是增加數(shù)據(jù)、查詢數(shù)據(jù)、刪除數(shù)據(jù)、更新數(shù)據(jù)。對(duì)于更新數(shù)據(jù)的操作的情況,多個(gè)表的操作和單個(gè)表沒有兩樣,在這里不舉例了。
添加數(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è)置屬性的方式和普通的類沒有什么區(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ì)話中。最后,調(diào)用endTransaction來(lái)提交事務(wù),并且關(guān)閉Hibernate會(huì)話。
數(shù)據(jù)查詢
在復(fù)雜的實(shí)體對(duì)象映射中,往往查詢也比較復(fù)雜。作為演示,我們?cè)谶@里也提供了幾個(gè)查詢方法,如例程5所示。
例程5 測(cè)試持久對(duì)象之間的映射關(guā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();
??? }
這里提供了三種查詢方法,分別是:
查詢給定id的Student的Address信息;
查詢給定id的Student的所有Courses信息;
查詢給定id的Student所屬的Classes信息。
在查詢時(shí),首先使用beginTransaction()方法創(chuàng)建一個(gè)Hibernate會(huì)話對(duì)象,并且開始一個(gè)新Hibernate事務(wù);然后通過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ì)話和一個(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ì)象,那么可以通過一個(gè)迭代來(lái)逐個(gè)刪除,如例程6中刪除courses的方法。
測(cè)試
在這里提供了在JSP中調(diào)用MapTestBean進(jìn)行測(cè)試的程序,具體代碼見maptest.jsp文件。在進(jìn)行測(cè)試前,請(qǐng)確保連接數(shù)據(jù)庫(kù)的配置完好,并且每個(gè)持久對(duì)象的配置都沒有錯(cuò)誤。如果配置出現(xiàn)困難,請(qǐng)參考本文的源代碼。
行動(dòng)起來(lái)
經(jīng)過兩篇文章由淺入深的學(xué)習(xí),希望能夠起到拋磚引玉的作用,相信讀者對(duì)Hibernate的認(rèn)識(shí)已經(jīng)有一個(gè)整體的把握。Hibernate由于它的易用性和良好的移植性等特點(diǎn),逐漸在企業(yè)級(jí)應(yīng)用開發(fā)中廣泛使用。Hibernate官方網(wǎng)站提供了非常好的使用手冊(cè),您可以參考它。如果您并非精通JDBC并且不想學(xué)習(xí)它,不妨考慮使用Hibernate;如果您在使用實(shí)體Bean之類的持久框架遇到困難,也許Hibernate可以助你一臂之力!