利用hibernateTools裡的相關工具類,使得java實體類(POJO)、hbm對應檔、資料庫表(Schema)之間可以相互轉化。也就是說,只要有其中一樣,就可以通過各種途徑得到其它兩樣。
如果手裡已經有了其中一樣東西,要想最快建立起應用的途徑自然是通過它來產生其它兩樣了。不過,我想在這裡討論的是那種從無到有,從想法到實現的那種建立全新應用的情況。那麼,自然而然就會有一個問題:“從哪裡開始?”。實體類?hbm?資料庫表?先應該建立哪一個,再通過它產生其它兩個?
這個問題我覺得應該從Hibernate架構的產生的初忠來考慮:為瞭解決“物件導向模型”與“關聯式模式”之間的“阻抗不匹配”。簡而言之,就是說在我們的類圖和E-R關係圖中各元素的對應關係很難把握,而且容易讓人產生概念的混淆,如“每一個資料庫表對應一個實體類”等錯誤想法,很容易讓人在設計實體時不知不覺扔掉很多物件導向模型的優秀思想。其實並不能說哪一個模型是錯,只是因為他們描述問題的方向不同。那麼,Hibernate架構就是把這兩個模型“映射”了起來,讓程式員可以在物件導向的世界裡完成對資料庫的查詢,而不必關心底層的資料庫表的結構。
說了那麼多,直接了當地講,我不贊成“先建立資料表,再通過這個資料表產生POJO和hbm檔案”這種方案。道理很簡單:如果先就去考慮資料庫,那麼我們隨後的設計勢必會受到這個資料庫的影響,不利於精確地描述我們的應用,Hibernate架構的好處也就體現不出來了(先就把資料庫搞定了還要Hibernate來幹什麼),產生的POJO不用說——內容多半很彆扭——因為你企圖從一個非物件導向的框框裡硬抽象出物件導向模型來(也許你會認為這是可以通過經驗來避免的,是的,確實是,不過你不覺得這樣一來工作複雜化了嗎?要考慮的東西增多了)。
物件導向模型是用來精確而自然地描述問題的,這是我的看法,它提供了包含、繼承等等機制,幾乎能把這個世界上的所有事物之間的關係精確地描述出來——它好比一門語言——怎麼方便你就怎麼說。那麼,先資料庫再POJO的做法就好比是先規定了你只能用哪些詞語之後讓你說話,而先POJO再資料庫就好比是讓你隨便說隨便發揮了,自然後者要好得多。
從軟體工程的角度講,需求——用例——實體這樣一趟走下來,POJO出現在資料庫前面是很自然的,在OOA階段就考慮資料庫是很不可取的做法(絕對一點講——是錯誤的)。
OK,剩下的POJO和hbm檔案之間應該先產生哪個呢?我覺得先產生誰都無所謂,都不會對我們的工程產生不利影響了。
hbm檔案作為POJO和資料庫之間的橋樑,從它入手的話會有一舉兩得的感覺,全是乾淨的XML檔案。當然,貌似利用xDoclet標籤在我們寫POJO代碼的時候自動產生hbm也是很不錯的感覺,這個下次再討論吧,現在給出一個完整可用而且最簡單的hbm產生POJO和Schema的樣本:
準備:
下載ant工具,我用的是apache-ant-1.7.0,當然,你要會用ant,怎麼使用請參考別處,這裡不介紹了。
下載hibernate-3.2.1.ga.zip,以及HibernateTools-3.2.0.beta9a.zip,這兩個壓縮包在hibernate官網上有:http://www.hibernate.org/6.html
資料庫的jdbc驅動程式,本例使用mysql,驅動程式為mysql-connector-java-5.0.4。
布置實驗場所:
建立“test”檔案夾(其實叫什麼都無所謂),再在test檔案夾下建立4個檔案夾:config、java、schema、lib。
解壓hibernate-3.2.1.ga.zip,把裡面的hibernate-3.2檔案夾放在你建立的lib檔案夾裡面。
解壓HibernateTools-3.2.0.beta9a.zip,找出hibernate-tools.jar,位置在:
/HibernateTools-3.2.0.beta9a/plugins/org.hibernate.eclipse_3.2.0.beta9a/lib/tools;
在與hibernate-tools.jar同樣的位置下找出freemarker.jar;
把hibernate-tools.jar和freemarker.jar都放入你建立的lib檔案夾裡。
最後把資料庫驅動程式:mysql-connector-java-5.0.4-bin.jar也放進lib檔案夾裡,
這樣,所需要的類資源都準備好了。
進入config檔案夾,首先寫hibernate config檔案,檔案名稱:hibernate.cfg.xml:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.username">root</property>
<property name="connection.password">XXXXXXXXXX</property><!--Your DB password here.-->
<property name="connection.url">jdbc:mysql://localhost:3306/courseChoosing</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<mapping resource="Student.hbm.xml" />
<mapping resource="Course.hbm.xml" />
</session-factory>
</hibernate-configuration>
以上代碼不用過多解釋,就是hibernate的設定檔,其中有串連資料庫的重要訊息。只是connection.password屬性注意修改成你自己的密碼就行了。那兩個mapping元素是本例要建立的兩個hbm檔案,待會兒我們再實際地建立它,現在不用管。
好了,hibernate設定檔寫好了之後,我們現在該討論一下我們將要進行的這個例子的與“業務”相關的話題了:
很簡單——我就拿“學生選課”這件事來做這個例子,具體描述是這樣的:“一門課可以被多個學生選,一個學生也可以選擇多門課。”很顯然,這是一個多對多關係。畫出實體類圖應該是這樣的(PowerDesigner12):
也就是說,在“學生”這個類中會有一個set,儲存了這個學生所選的所有的課;同時,在“課程”這個類中也會有一個set,儲存了所有要上這門課的學生。
弄清楚了類圖之後,讓我們用hbm檔案來分別描述這兩個類:
還是在config檔案夾下,建立Course.hbm.xml和Student.hbm.xml,
Course.hbm.xml的內容如下:
<?xml version = "1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package = "courseChoosing">
<class name = "Course" table = "course">
<id name = "id" column = "id" type = "long">
<generator class = "native" />
</id>
<property name = "name" type = "string" length = "50" />
<property name = "credit" type = "integer" />
<property name = "totalClasses" type = "integer" />
<set name = "students" table = "courseChoosing" inverse = "true">
<key column = "courseId" />
<many-to-many class = "Student" column = "studentId" />
</set>
</class>
</hibernate-mapping>
Student.hbm.xml的內容如下:
<?xml version = "1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package = "courseChoosing">
<class name = "Student" table = "student">
<id name = "id" column = "id" type = "long">
<generator class = "native" />
</id>
<property name = "stuNo" type = "string" length = "10" />
<property name = "name" type = "string" length = "20" />
<property name = "gender" type = "character" />
<set name = "courses" table = "courseChoosing" inverse = "false">
<key column = "studentId" />
<many-to-many class = "Course" column = "courseId" />
</set>
</class>
</hibernate-mapping>
這兩個檔案是純粹的hbm檔案描述實體類的方式,看上去很簡潔,層次很清晰。需要仔細看明白的是其中的那個set元素:
name屬性是這個set的名字,也就是最後產生的java代碼中的這個set的名字;table屬性是這個set對應的資料庫表的名字,由於這個是多對多關係,所以需要另外建立一個表來儲存學生和課程之間的選擇關係,不過這種細節不用管,hibernatetools會做好一切。最重要的是那個inverse屬性,它決定了多對多關係的兩邊到底由誰來管理雙方的關係資料。inverse設為“true”的一方將不負責維護雙方的關係資料,本例中考慮實際情況,決定讓“學生”類來維護這個選課關係(因為從來只有學生選課或退出選課,而沒有課選學生或課踢除學生的情況),於是將Course類裡的set的inverse屬性設為“true”,Student裡的inverse為“false”。順便提一句,如果不指明inverse屬性,那麼預設為false。many-to-many元素說明了他們之間的關係,class屬性指明了當前set中對象的類型,column屬性指明了關係表(courseChoosing)中的外碼名。
到此為止,與業務相關的東西已經描述完畢,剩下的就是編寫ant指令碼來實際產生ddl和java檔案了。
在test檔案夾下,建立build.xml檔案,其內容如下:
<?xml version="1.0"?>
<project name = "test" default = "hbm2java">
<property name = "configuration-files.dir" value = "config" />
<property name = "java.code.dir" value = "java" />
<property name = "schema.dir" value = "schema" />
<property name = "lib.dir" value = "lib" />
<property name = "hibernate3.dir" value = "${lib.dir}/hibernate-3.2" />
<path id = "hibernate3.path">
<pathelement location = "${hibernate3.dir}/hibernate3.jar" />
<fileset dir = "${hibernate3.dir}">
<include name = "**/*.jar" />
</fileset>
</path>
<path id = "mysql.jdbc.driver.path">
<pathelement location = "${lib.dir}/mysql-connector-java-5.0.4-bin.jar" />
</path>
<path id = "hibernate-tools.path">
<pathelement location = "${lib.dir}/hibernate-tools.jar" />
</path>
<path id = "freemarker.path">
<pathelement location = "${lib.dir}/freemarker.jar" />
</path>
<path id = "all-in-one.path">
<path refid = "hibernate3.path" />
<path refid = "mysql.jdbc.driver.path" />
<path refid = "hibernate-tools.path" />
<path refid = "freemarker.path" />
<pathelement location = "${configuration-files.dir}" />
</path>
<target name = "hbm2java">
<taskdef name = "hbm2java" classname = "org.hibernate.tool.ant.HibernateToolTask" classpathref = "all-in-one.path" />
<hbm2java destdir = "${java.code.dir}">
<configuration configurationfile = "${configuration-files.dir}/hibernate.cfg.xml" />
<hbm2java jdk5 = "true" />
</hbm2java>
</target>
<target name = "hbm2ddl">
<taskdef name = "hbm2ddl" classname = "org.hibernate.tool.ant.HibernateToolTask" classpathref = "all-in-one.path" />
<hbm2ddl destdir = "${schema.dir}">
<configuration configurationfile = "${configuration-files.dir}/hibernate.cfg.xml" />
<hbm2ddl export="true" console="false" create="true" update="false" drop="false" outputfilename="courseChoosing.sql" />
</hbm2ddl>
</target>
</project>
很平常的ant指令碼,需要注意的是那兩個特殊的任務:hbm2java和hbm2ddl,這兩個任務是hibernatetools裡帶的,需要定義出來,就不多說了。
好了,安裝好ant之後就可以運行這個指令碼了,產生java檔案:
產生資料庫表:
大功告成了,欣賞一下結果:
在java檔案夾中產生的java檔案代碼:
Course.java檔案:
package courseChoosing;
// Generated 2007-3-7 15:38:42 by Hibernate Tools 3.2.0.b9
import java.util.HashSet;
import java.util.Set;
/**
* Course generated by hbm2java
*/
public class Course implements java.io.Serializable {
private long id;
private String name;
private Integer credit;
private Integer totalClasses;
private Set<Student> students = new HashSet<Student>(0);
public Course() {
}
public Course(String name, Integer credit, Integer totalClasses, Set<Student> students) {
this.name = name;
this.credit = credit;
this.totalClasses = totalClasses;
this.students = students;
}
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCredit() {
return this.credit;
}
public void setCredit(Integer credit) {
this.credit = credit;
}
public Integer getTotalClasses() {
return this.totalClasses;
}
public void setTotalClasses(Integer totalClasses) {
this.totalClasses = totalClasses;
}
public Set<Student> getStudents() {
return this.students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
}
Student.java檔案中的內容:
package courseChoosing;
// Generated 2007-3-7 15:38:42 by Hibernate Tools 3.2.0.b9
import java.util.HashSet;
import java.util.Set;
/**
* Student generated by hbm2java
*/
public class Student implements java.io.Serializable {
private long id;
private String stuNo;
private String name;
private Character gender;
private Set<Course> courses = new HashSet<Course>(0);
public Student() {
}
public Student(String stuNo, String name, Character gender, Set<Course> courses) {
this.stuNo = stuNo;
this.name = name;
this.gender = gender;
this.courses = courses;
}
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
public String getStuNo() {
return this.stuNo;
}
public void setStuNo(String stuNo) {
this.stuNo = stuNo;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Character getGender() {
return this.gender;
}
public void setGender(Character gender) {
this.gender = gender;
}
public Set<Course> getCourses() {
return this.courses;
}
public void setCourses(Set<Course> courses) {
this.courses = courses;
}
}
schema檔案夾中產生的courseChoosing.sql檔案內容:
create table course (id bigint not null auto_increment, name varchar(50), credit integer, totalClasses integer, primary key (id));
create table courseChoosing (studentId bigint not null, courseId bigint not null, primary key (studentId, courseId));
create table student (id bigint not null auto_increment, stuNo varchar(10), name varchar(20), gender char(1), primary key (id));
alter table courseChoosing add index FK42CD624F924EFB30 (courseId), add constraint FK42CD624F924EFB30 foreign key (courseId) references course (id);
alter table courseChoosing add index FK42CD624F417DBA12 (studentId), add constraint FK42CD624F417DBA12 foreign key (studentId) references student (id);
產生的資料庫表(MySQL Administrator):