2007年8月5日 星期日

(2006.08月號--151期) 如何以Maven協助Hibernate開發

在今日的企業級應用開發環境中,物件導向開發方式已成為主流,但在實際的應用中,物件只存在於程式與記憶體之中,並不能直接進行儲存。如果想要永久保存物件狀態,則必須要進行物件的持久化,也就是把物件狀態儲存進專門的資料庫系統中,而目前最廣泛使用的關聯式資料庫(RDBMS),並不支援儲存物件導向數據資料。


因此傳統的關聯式資料庫程式設計,必須直接在程式中以Hard code的方式撰寫SQL語法進行開發,而以Hard code撰寫SQL語法的方式,相對的也代表了開發的應用程式無法跨資料庫平台。雖然JDBC統一了Java程式與資料庫之間的操作介面,讓開發人員可以不用了解與資料庫相關特定的API操作,然而自行撰寫SQL語法或再次將SQL語法進行封裝仍是不可避免的事,而在物件導向程式設計之中,物件與關聯式資料並無法以簡單的方式進行轉換,導致了資料永續性的開發上受到了先天的限制。


ORM(Object/Relational Mapping)物件-關聯映射技術,在該問題的處理上已經有完善的解決方案,而目前在開放原始碼的技術中,最受人矚目的ORM實作,應該就是Hibernate了。在本期中筆者將介紹如何使用Maven協助Hibernate進行快速開發,而對於Hibernate的基本觀念,筆者在此並不詳述,請參考之前刊載於RunPC中的相關文章。


註:關於Hibernate文章,請參考RunPC128129130132期,資料永續層解決方案Hibernate的相關介紹,作者為Mark Ho


設定hibernatedoclet環境

在開發過程中Hibernate進行物件與關聯式資料的處理過程中,需要有一份映射文件,用以描述物件與關聯式資料的轉換關係,例如:資料型態的對應、資料欄位長度的限制、單向關聯、雙向關聯、一對多、一對一與多對多關聯等等的設定。一般而言映射文件命名通常為*.hbm.xml


然而在實際開發上當物件作了修改或重構後,必須對映射文件進行維護。如此的開發方式相當的不直覺與不方便。並且學習映射文件的撰寫不是一件簡單的事。


因此我們換一個角度思考,如果在物件的設計中,順手將這些轉換關係的資訊寫在物件中,由物件編譯過程中直接幫我們產生相關的映射文件,則我們只要維護一份原始碼即可,當進行修改與重構的過程中,映射檔案的變更也將立即的反應出來。在此我們將使用XDoclet進行Code generate來達到這個目的。


註:有關於XDoclet相關觀念的文章,請參考RunPC130131期,XDoclet入門篇與進階篇,作者為歐宣修。


首先請參考筆者於148期中的Maven文章,使用Maven Genapp Plugin產生專案,並且參考設定 1project.xml進行修改,在該檔中可以看到我們在Maven相依的plugin套件中加入了maven-xdoclet-plugin。接著參考設定 2project.properties加入設定參數。說明請參考表 1中關於hibernatedoclet的參數設定。最後再對maven.xml進行設定,參考設定 3中可知當我們執行java:compile這個goal前會先行運作xdoclet:hibernatedoclet協助我們產生Mapping file


設定 1 project.xml

<?xml version="1.0" encoding="UTF-8"?>

<project>

<pomVersion>3</pomVersion>

<artifactId>hibernate-example</artifactId>

<groupId>hibernate-example</groupId>

<name>Example Hibernate Application</name>

<currentVersion>1.0</currentVersion>

<dependencies>

<dependency>

<groupId>xdoclet</groupId>

<artifactId>maven-xdoclet-plugin</artifactId>

<version>1.2.3</version>

<type>plugin</type>

</dependency>

中間省略…

</dependencies>

中間省略…

</project>


設定 2 project.properties

#####################################

# hibernatedoclet properties

#####################################

maven.xdoclet.hibernatedoclet.fileset.0=true

maven.xdoclet.hibernatedoclet.fileset.0.sourcedir=${maven.src.dir}/java

maven.xdoclet.hibernatedoclet.fileset.0.include=**/*.java

maven.xdoclet.hibernatedoclet.fileset.0.exclude=

maven.xdoclet.hibernatedoclet.hibernate.0=true

maven.xdoclet.hibernatedoclet.hibernate.0.Version=3.0

maven.xdoclet.hibernatedoclet.destDir=${maven.build.dest}


設定 3 maven.xml

<?xml version="1.0" encoding="UTF-8"?>

<project

xmlns:j="jelly:core"

xmlns:ant="jelly:ant">

<preGoal name="java:compile">

<attainGoal name="xdoclet:hibernatedoclet"/>

</preGoal>

</project>


1 maven-xdoclet-plugin設定

參數

說明

maven.xdoclet.hibernatedoclet.fileset.[index]

設定hibernatedoclet引用的檔案集設定,index值為0~910組設定,也就是說最多可以針對10組路徑下的原始檔進行hibernatedocletCode generate,預設index=0為啟用。若指定的maven.xdoclet.hibernatedoclet.fileset.[index]未設定,則以下相對應的檔案集設定即無效。

maven.xdoclet.hibernatedoclet.fileset.[index].sourcedir

指定檔案集原始碼放置路徑,預設為${maven.src.dir}/java

maven.xdoclet.hibernatedoclet.fileset.[index].include

指定檔案集引入檔案規則,預設為**/*.java

maven.xdoclet.hibernatedoclet.fileset.[index].exclude

指定檔案集排除檔案規則,預設不排除任何檔。

maven.xdoclet.hibernatedoclet.hibernate.[index]

設定對maven.xdoclet.hibernatedoclet.fileset.[index]進行hibernatedoclet是否生效,預設為true,若為false則對指定的檔案集不執行hibernatedoclet

maven.xdoclet.hibernatedoclet.hibernate.[index].Version

設定對maven.xdoclet.hibernatedoclet.fileset.[index]進行hibernatedoclet所產生的*.hbm.xml檔案可使用於hibernate那個版本,預設為1.1版,目前可設定的值為目前有1.12.02.13.0

maven.xdoclet.hibernatedoclet.destDir

所有檔案集產生出的*.hbm.xml放置的目的路徑。


建立Domain Object並產生Mapping File

當上一節的設定完成後,我們正式建立Domain Object,並試圖產生Hibernate所需要的Mapping file。這裏我們

是以POJO定義Domain Object,而POJO就是所謂的Plain Ordinary Java Object,字面上來講就是無格式普通Java 物件,簡單的可以理解爲一個不包含邏輯程式碼的值物件。


我們在此建立二個POJO,分別是User(使用者)Role(所屬角色)作為開始。這時Maven專案架構應該如資料結構 1所示,圖 1展示了類別屬性與其setget相關method。接著參考程式 1、程式 2分別在類別中加入<hibernatedoclet>Tag設定,在此我們簡單的了解一下相關屬性用途,若讀者希望了解更詳細的屬性設定,可參考XDoclet官方網站http://xdoclet.sourceforge.net/xdoclet/tags/hibernate-tags.html


  1. @hibernate.class:該Tag用於宣告定義持久化類別,需放置於Class宣告的上方。

    1. table:指定持久化類別對應資料庫Table名稱。

  2. @hibernate.id:定義主鍵欄位相關屬性。

    1. column:指定欄位對應資料庫欄位名稱,若未指定則使用相對應的property名稱。

    2. generator-class:主鍵產生器屬性,目前有uuid.hexuuid.stringincrementassignednativeidentitysequencehiloseqhiloforeign可供設定。

  3. @hibernate.property:定義持久化類別對應資料庫欄位相關屬性。

    1. column:指定欄位對應資料庫欄位名稱,若未指定則使用相對應的property名稱。

    2. length:指定對應資料庫欄位長度。

    3. not-null:是否允許欄位存放null值。

    4. unique:是否允許資料庫欄位值重覆

    5. type:對應資料庫欄位型態

另外,讀者可能發現,在範例中的二個POJO都實現了java.io.Serializable介面,這是Hibernate規範中所提到的,但目前大多數資料並未說明為何必須實現Serializable。事實上Hibernate並不要求持久化類必須實現Serializable介面,但是對於採用分散式架構的Java應用,當Java物件在不同的分散節點之間傳輸時(例如:RMI),這個物件所屬的類別必須實現Serializable介面,此外,在Java Web應用中,如果希望對HttpSession中存放的Java物件進行持久化,那麼這個Java物件所屬的類也必須實現Serializable介面,因此若只拿Hibernate作單純資料庫應用可不遵守該規定外,HibernateJ2EE的應用上都是必須實現Serializable的。


經過maven的設定與撰寫完POJO後,在Console下執行maven java:compile,當指令運作結束後,在[hibernate_example/target/classes/demo/model]路徑下將會出現User.hbm.xmlRole.hbm.xmlHibernate Mapping file

資料結構 1

hibernate-example

|--src

| --main

| --java

| --demo

| --model

| |--Role.java

| --User.java

|--maven.xml

|--project.properties

--project.xml


1 Domain Object類別圖


程式 1 User.java

package demo.model;

import java.io.Serializable;


/**

* @hibernate.class table="app_user"

*/

public class User implements Serializable {

private Long id;

private String username;

private String password;


/**

* @hibernate.id column="id" generator-class="native"

*/

public Long getId() {

return id;

}


/**

* @hibernate.property length="50" not-null="true" unique="true"

*/

public String getUsername() {

return username;

}


/**

* @hibernate.property column="password" not-null="true"

*/

public String getPassword() {

return password;

}

…set method 省略…

}


程式 2 Role.java

package demo.model;

import java.io.Serializable;


/**

* @hibernate.class table="role"

*/

public class Role implements Serializable {

private Long id;

private String name;

private String description;


/**

* @hibernate.id column="id" generator-class="native"

*/

public Long getId() {

return id;

}


/**

* @hibernate.property column="name" length="20"

*/

public String getName() {

return name;

}


/**

* @hibernate.property column="description" length="64"

*/

public String getDescription() {

return description;

}

…set method 省略…

}


使用Hibernate Plugin建立Table Schema

到目前為止我們設定了hibernatedoclet、建立了POJO的類別並自動產生了相關的Mapping file。假設這時資料庫Schema早已建立好的話,就可以開始使用Hibernate進行DAO的開發與測試了。倘若Schema尚未建立呢?需要自行建立嗎?而自行建立則又會引發另一個問題,POJO的設計是基於Schema所建立的。當日後Schema修改時也必須對應相關的POJOMapping file進行維護。一次、二次還可以接受,但在專案開發的過程中需求永遠在變化,很難保證Schema不會一直變更,一旦次數一多,將引發難以重構的災難。


Ant中可以透過net.sf.hibernate.tool.hbm2ddl.SchemaExportTask協助我們將*.hbm.xml再次轉化成相對應資料庫的Schema,而在Maven上可以使用Hibernate Plugin協助進行SchemaExport。基於某些因素Hibernate Plugin最新版本為1.3並且已有一段時間沒有新版本釋出,該版本並未支援Hibernate 3.X,若讀者使用Hibernate 2開發的話,可以直接進行下載。在本次範例中我們將下載經過1.3版本修改的Hibernate Plugin1.4 (請讀者參考相關資源4進行下載)


[%MAVEN_HOME%/plugins]下的1.3版移除,放置下載完成的maven-hibernate-plugin-1.4.jar於該資料夾內即完成了hibernate plugin的安裝。接著參考設定 4project.xml中加入相依套件,此處的設定中我們加入了maven-hibernate-plugin的設定與Hibernate進行SchemaExport時所需的套件,另外為了示範Hibernate跨資料庫平台的特色,在此引入而PostgreSqlHsqldb二個開放原始碼資料庫的JDBC協助進行測試。


在進行SchemaExport功能時可以設定是否正式進行資料庫的更新,或者只是單純的產生資料庫Schema的文字檔供開發人員進行修改或使用。為此必須正確設定Hibernate與資料庫連結的相關參數,參考設定 5與設定 6比較範例中二種資料庫設定的異同處,而關於hibernate.dialect參數的設定,請參考Hibernate官方參考手冊。


最後參考設定 7加入Hibernate plugin所需的參數,讀者對照表 2即可知相關參數的意義為何。當設定完成後於Console下執行maven hibernate:schema-export即可進行SchemaExport,在此我們分別為了PostgreSqlHsqldb作了SchemaExport,請參考圖 2與圖 3的結果。若讀者不希望直接進行資料庫的更新,則可將project.properties檔中參數maven.hibernate.text設為yes再次進行hibernate:schema-export,即可於[target/schema/schema.sql]檔中查看匯出的資料庫SQL語法。


hibernate:schema-export這個goal可以協助我們進行建立資料庫Schema,而當進行POJO的修改並重新產生Mapping file時,可運行maven hibernate:schema-update,該指令將可在不移除資料表的清況下進行資料庫Schema的更新,對於DataBase Schema的重構有很大的幫助。


設定 4 project.xml增添相依套件

<dependency>

<groupId>maven</groupId>

<artifactId>maven-hibernate-plugin</artifactId>

<version>1.4</version>

<type>plugin</type>

</dependency>

<dependency>

<groupId>postgresql</groupId>

<artifactId>postgresql</artifactId>

<version>8.1-407.jdbc3</version>

</dependency>

<dependency>

<groupId>hsqldb</groupId>

<artifactId>hsqldb</artifactId>

<version>1.7.3.3</version>

</dependency>

<dependency>

<groupId>geronimo-spec</groupId>

<artifactId>geronimo-spec-jta</artifactId>

<version>1.0.1B-rc4</version>

</dependency>

<dependency>

<groupId>commons-lang</groupId>

<artifactId>commons-lang</artifactId>

<version>2.1</version>

</dependency>

<dependency>

<groupId>hibernate</groupId>

<artifactId>hibernate</artifactId>

<version>3.0.5</version>

</dependency>


設定 5 database-postgresql.properties

hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

hibernate.connection.url=jdbc:postgresql://localhost/demo

hibernate.connection.driver_class=org.postgresql.Driver

hibernate.connection.show_sql=true

hibernate.connection.username=test

hibernate.connection.password=test


設定 6 database-hsqldb.properties

hibernate.dialect=org.hibernate.dialect.HSQLDialect

hibernate.connection.url=jdbc:hsqldb:hsql://localhost/demo

hibernate.connection.driver_class=org.hsqldb.jdbcDriver

hibernate.connection.show_sql=true

hibernate.connection.username=sa

hibernate.connection.password=


設定 7 project.properties增添相關屬性設定

#####################################

# hibernate properties

#####################################

maven.hibernate.properties=${basedir}/database-postgresql.properties

#maven.hibernate.properties=${basedir}/database-hsqldb.properties

maven.hibernate.quiet=no

maven.hibernate.text=no

maven.hibernate.drop=no

maven.hibernate.delimiter=;

maven.hibernate.output.dir=${maven.build.dir}/schema

maven.hibernate.output.file=${maven.hibernate.output.dir}/schema.sql


2 maven-hibernate-plugin設定

參數

說明

maven.hibernate.properties

hibernate參數設定檔放置路徑,無論直接進行資料庫的更新或單純產出Schema的文字檔,皆必須要進行該項設定主要以hibernate.dialect參數決定使用何種資料庫方言。

maven.hibernate.text

若為yes則執行Schema Export時將直接匯出SQL成文字檔,並不會進行資料庫的更新,若設為no則直接進行資料庫Schema的更新,預設為no

maven.hibernate.quiet

是否顯示詳細的Schema Export資訊,例:當maven.hibernate.text=nomaven.hibernate.quiet=yes時,將直接進行資料庫Schema的更新,Console下不會顯示任何訊息,預設為yes

maven.hibernate.drop

是否只執行drop資料表的動作,若maven.hibernate.text=yes則匯出SQL檔將只包含drop tableSQL語法。

maven.hibernate.delimiter

分隔SQL Commands的定義符號,預設值為空字串,而大多數狀況使用”;”作為分隔符號。

maven.hibernate.output.dir

maven.hibernate.text=yes,匯出檔放置資料夾,預設為${maven.build.dir}/schema

maven.hibernate.output.file

maven.hibernate.text=yes,匯出檔放置路徑,預設為${maven.hibernate.output.dir}/${maven.final.name}-schema.sql


2 PostgreSql SchemaExport結果

3 Hsqldb SchemaExport結果


結論

Hibernate是一個開放原始碼的物件關聯映射技術,針對了JDBC進行了輕量化的將關聯資料轉化為物件封裝,使得開發人員可以隨心所欲的使用物件導向思維來駕馭資料庫。而在本期的介紹中我們了解了使用Maven協助Hibernate建立Domain Object的一種應用,而除了本次範例的方式外,Hibernate也可以使用Middlegen的方式透過現有的資料庫Schema產生出Mapping file,又或著從Mapping file建立出SchemaJava Domain Object。因此可依專案的狀況進行彈性的調整。


Hibernate的應用可以切入在任何使用JDBC的場合,既可以應用於單純的Java Application,也可以在Servlet/JSPWeb應用程式中使用,更可以應用在EJBJ2EE架構中取代CMP,完成資料持久化的重任。倘若更深入Hibernate的架構與觀念中,則將更了解物件關聯映射技術的最佳化實踐。