創(chuàng)建數(shù)據(jù)庫(kù)Schema 在本例中,與Customer類對(duì)應(yīng)的數(shù)據(jù)庫(kù)表名為CUSTOMERS,它在MySQL數(shù)據(jù)庫(kù)中的DDL定義如下:
create table CUSTOMERS (
ID bigint not null primary key,
NAME varchar(15) not null,
EMAIL varchar(128) not null,
PASSWORD varchar(8) not null,
PHONE int ,
ADDRESS varchar(255),
SEX char(1) ,
IS_MARRIED bit,
DESCRIPTION text,
IMAGE blob,
BIRTHDAY date,
REGISTERED_TIME timestamp
);
CUSTOMERS表有一個(gè)ID字段,它是表的主鍵,它和Customer類的id屬性對(duì)應(yīng)。CUSTOMERS表中的字段使用了各種各樣的SQL類型
2.4 創(chuàng)建對(duì)象-關(guān)系映射文件
Hibernate采用XML格式的文件來(lái)指定對(duì)象和關(guān)系數(shù)據(jù)之間的映射。在運(yùn)行時(shí),Hibernate將根據(jù)這個(gè)映射文件來(lái)生成各種SQL語(yǔ)句。在本例中,將創(chuàng)建一個(gè)名為Customer.hbm.xml的文件,它用于把Customer類映射到CUSTOMERS表,這個(gè)文件應(yīng)該和Customer.class文件存放在同一個(gè)目錄下。例程2-3為Customer.hbm.xml文件的代碼。
例程2-3 Customer.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="mypack.Customer"
table="CUSTOMERS">
<id name="id" column="ID" type="long">
<generator class="increment"/>
</id>
<property name="name"
column="NAME" type="string"
not-null="true" />
<property name="email"
column="EMAIL" type="string"
not-null="true" />
<property name="password"
column="PASSWORD" type="string"
not-null="true"/>
<property name="phone"
column="PHONE" type="int" />
<property name="address"
column="ADDRESS" type="string" />
<property name="sex"
column="SEX" type="character"/>
<property name="married"
column="IS_MARRIED" type="boolean"/>
<property name="description"
column="DESCRIPTION" type="text"/>
<property name="image"
column="IMAGE" type="binary"/>
<property name="birthday"
column="BIRTHDAY" type="date"/>
<property name="registeredTime"
column="REGISTERED_TIME"
type="timestamp"/>
</class>
</hibernate-mapping>
2.4.1 映射文件的文檔類型定義(DTD)
在例程2-3的Customer.hbm.xml文件的開頭聲明了DTD(Document Type Definition,文檔類型定義),它對(duì)XML文件的語(yǔ)法和格式做了定義。Hibernate的XML解析器將根據(jù)DTD來(lái)核對(duì)XML文件的語(yǔ)法。
每一種XML文件都有獨(dú)自的DTD文件。Hibernate的對(duì)象-關(guān)系映射文件使用的DTD文件的下載網(wǎng)址為:http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd。此外,在Hibernate軟件包的src
etsfhibernate目錄下也提供了hibernate-mapping-2.0.dtd文件。在這個(gè)文件中,描述頂層元素的代碼如下:
<!ELEMENT hibernate-mapping (meta*,
import*, (class|subclass|joined-subclass)*,
query*,sql-query*)>
描述頂層元素的子元素的代碼如下:
<!ELEMENT class (
meta*,
(cache|jcs-cache)?,
(id|composite-id),
discriminator?,
(version|timestamp)?,
(property|many-to-one|one-to-one
|component|dynamic-component|any
|map|set|list|bag|idbag|array
|primitive-array)*,
((subclass*)|(joined-subclass*))
)>
元素是對(duì)象-關(guān)系映射文件的根元素,其他元素(即以上DTD代碼中括號(hào)以內(nèi)的元素,如子元素)必須嵌入在元素以內(nèi)。在元素中又嵌套了好多子元素。
在以上DTD代碼中,還使用了一系列的特殊符號(hào)來(lái)修飾元素,表2-3描述了這些符號(hào)的作用。在創(chuàng)建自己的對(duì)象-關(guān)系映射文件時(shí),如果不熟悉某種元素的語(yǔ)法,可以參考DTD文件。
此外,在映射文件中,父元素中的各種子元素的定義必須符合特定的順序。例如,根據(jù)元素的DTD可以看出,必須先定義子元素,再定義子元素,以下映射代碼顛倒了和子元素的位置:
<class name="mypack.Customer"
table="CUSTOMERS">
<property name="name"
column="NAME" type="string"
not-null="true" />
<property name="email"
column="EMAIL"
type="string" not-null="true" />
<id name="id" column="ID" type="long">
<generator class="increment"/>
</id>
……
</class>
Hibernate的XML解析器在運(yùn)行時(shí)會(huì)拋出MappingException:
[java] 21:27:51,610 ERROR XMLHelper:
48 - Error parsing XML:
XML InputStream (24)
The content of element type "class"
must match "(meta*,(cache|jcs-cache)?,
(
id|composite-id),
discriminator?,(version|timestamp)?,
(property|many-to-one|one-to-one|component|
dynamic-component|any|map|set
|list|bag|idbag|array|primitive-array)*,
(subclass*|joined-subclass*))".
[java] net.sf.hibernate.MappingException:
Error reading resource:
mypack/Customer.hbm.xml
at net.sf.hibernate.cfg.Configuration.addClass
(Configuration.java:357)
2.4.2 把Customer持久化類映射到CUSTOMERS表
例程2-3的Customer.hbm.xml文件用于映射Customer類。如果需要映射多個(gè)持久化類,那么既可以在同一個(gè)映射文件中映射所有類,也可以為每個(gè)類創(chuàng)建單獨(dú)的映射文件,映射文件和類同名,擴(kuò)展名為"hbm.xml"。后一種做法更值得推薦,因?yàn)樵趫F(tuán)隊(duì)開發(fā)中,這有利于管理和維護(hù)映射文件。
元素指定類和表的映射,它的name屬性設(shè)定類名,table屬性設(shè)定表名。以下代碼表明和Customer類對(duì)應(yīng)的表為CUSTOMERS表:
<class name="mypack.Customer" table="CUSTOMERS">
如果沒有設(shè)置元素的table屬性,Hibernate將直接以類名作為表名,也就是說,在默認(rèn)情況下,與mypack.Customer類對(duì)應(yīng)的表為Customer表。
元素包含一個(gè)子元素及多個(gè)子元素。子元素設(shè)定持久化類的OID和表的主鍵的映射。以下代碼表明Customer類的id屬性和CUSTOMERS表中的ID字段對(duì)應(yīng)。
<id name="id" column="ID" type="long">
<generator class="increment"/>
</id>
元素的子元素指定對(duì)象標(biāo)識(shí)符生成器,它負(fù)責(zé)為OID生成惟一標(biāo)識(shí)符。本書第5章(映射對(duì)象標(biāo)識(shí)符)詳細(xì)介紹了Hibernate提供的各種對(duì)象標(biāo)識(shí)符生成器的用法。
子元素設(shè)定類的屬性和表的字段的映射。子元素主要包括name、type、column和not-null屬性。
1.元素的name屬性
元素的name屬性指定持久化類的屬性的名字。
2.元素的type屬性
元素的type屬性指定Hibernate映射類型。Hibernate映射類型是Java類型與SQL類型的橋梁。表2-4列出了Customer類的屬性的Java類型、Hibernate映射類型,以及CUSTOMERS表的字段的SQL類型這三者之間的對(duì)應(yīng)關(guān)系。
表2-4 Java類型、Hibernate映射類型以及SQL類型之間的對(duì)應(yīng)關(guān)系
Customer類的屬性 Java類型 Hibernate映射類型 CUSTOMERS表的字段 SQL類型
name java.lang.String string NAME VARCHAR(15)
email java.lang.String string EMAIL VARCHAR(128)
password java.lang.String string PASSWORD VARCHAR(8)
phone int int PHONE INT
address java.lang.String string ADDRESS VARCHAR(255)
sex char character SEX CHAR(1)
married boolean boolean IS_MARRIED BIT
description java.lang.String text DESCRIPTION TEXT
image byte[] binary IMAGE BLOB
birthday java.sql.Date date BIRTHDAY DATE
registeredTime java.sql.Timestamp timestamp REGISTERED_TIME TIMESTAMP
從表2-4看出,如果Customer類的屬性為java.lang.String類型,并且與此對(duì)應(yīng)的CUSTOMERS表的字段為VARCHAR類型,那么應(yīng)該把Hibernate映射類型設(shè)為string,例如:
<property name="name"
column="NAME" type="string"
not-null="true" />
如果Customer類的屬性為java.lang.String類型,并且與此對(duì)應(yīng)的CUSTOMERS表的字段為TEXT類型,那么應(yīng)該把Hibernate映射類型設(shè)為text,例如:
<property name="description" column="DESCRIPTION" type="text"/>
如果Customer類的屬性為byte[]類型,并且與此對(duì)應(yīng)的CUSTOMERS表的字段為BLOB類型,那么應(yīng)該把Hibernate映射類型設(shè)為binary,例如:
<property name="image" column="IMAGE" type="binary"/>
如果沒有顯式設(shè)定映射類型,Hibernate會(huì)運(yùn)用Java反射機(jī)制先識(shí)別出持久化類的屬性的Java類型,然后自動(dòng)使用與之對(duì)應(yīng)的默認(rèn)的Hibernate映射類型。例如,Customer類的address屬性為java.lang.String類型,與java.lang.String對(duì)應(yīng)的默認(rèn)的映射類型為string,因此以下兩種設(shè)置方式是等價(jià)的:
<property name="address" column="ADDRESS"/>
或者:
<property name="address" type="string" />
對(duì)于Customer類的description屬性,盡管它是java.lang.String類型,由于CUSTOMERS表的DESCRIPTION字段為text類型,因此必須顯式地把映射類型設(shè)為text。
3.元素的not-null屬性
如果元素的not-null屬性為true,表明不允許為null,默認(rèn)為false。例如以下代碼表明不允許Customer類的name屬性為null:
<property name="name" column="NAME" type="string" not-null="true" />
Hibernate在持久化一個(gè)Customer對(duì)象時(shí),會(huì)先檢查它的name屬性是否為null,如果為null,就會(huì)拋出以下異常:
net.sf.hibernate.PropertyValueException:
not-null property references
a null or transient value:
mypack.Customer.name
如果數(shù)據(jù)庫(kù)中CUSTOMERS表的NAME字段不允許為null,但在映射文件中沒有設(shè)置not-null屬性:
<property name="name" column="NAME" type="string" />
那么Hibernate在持久化一個(gè)Customer對(duì)象時(shí),不會(huì)先檢查它的name屬性是否為null,而是直接通過JDBC API向CUSTOMERS表插入相應(yīng)的數(shù)據(jù),由于CUSTOMERS表的NAME字段設(shè)置了not null約束,因此數(shù)據(jù)庫(kù)會(huì)拋出錯(cuò)誤:
708 ERROR JDBCExceptionReporter:
58 - General error, message from server:
"Column NAME cannot be null"
值得注意的是,對(duì)于實(shí)際Java應(yīng)用,當(dāng)持久化一個(gè)Java對(duì)象時(shí),不應(yīng)該依賴Hibernate或數(shù)據(jù)庫(kù)來(lái)負(fù)責(zé)數(shù)據(jù)驗(yàn)證。在四層應(yīng)用結(jié)構(gòu)中,應(yīng)該由表述層或者業(yè)務(wù)邏輯層負(fù)責(zé)數(shù)據(jù)驗(yàn)證。例如對(duì)于Customer對(duì)象的name屬性,事實(shí)上在表述層就能檢查name屬性是否為null,假如表述層、業(yè)務(wù)邏輯層和Hibernate持久化層都沒有檢查name屬性是否為null,那么數(shù)據(jù)庫(kù)層會(huì)監(jiān)測(cè)到NAME字段違反了數(shù)據(jù)完整性約束,從而拋出異常,如圖2-2所示,包含非法數(shù)據(jù)的Customer對(duì)象從表述層依次傳到數(shù)據(jù)庫(kù)層,隨后從數(shù)據(jù)庫(kù)層拋出的錯(cuò)誤信息又依次傳到表述層,這種做法顯然會(huì)降低數(shù)據(jù)驗(yàn)證的效率。
既然如此,把元素的not-null屬性設(shè)為true,有何意義呢?這主要是便于在軟件開發(fā)和測(cè)試階段能捕獲表述層或者業(yè)務(wù)邏輯層應(yīng)該處理而未處理的異常,提醒開發(fā)人員在表述層或者業(yè)務(wù)邏輯層中加入必要的數(shù)據(jù)驗(yàn)證邏輯。
4.元素的column屬性
元素的column屬性指定與類的屬性映射的表的字段名。以下代碼表明和address屬性對(duì)應(yīng)的字段為ADDRESS字段:
<property name="address" column= "ADDRESS" type="string"/>
如果沒有設(shè)置< property >元素的column屬性,Hibernate將直接以類的屬性名作為字段名,也就是說,在默認(rèn)情況下,與Customer類的address屬性對(duì)應(yīng)的字段為address字段。
元素還可以包括子元素,它和元素的column屬性一樣,都可以設(shè)定與類的屬性映射的表的字段名。以下兩種設(shè)置方式是等價(jià)的:
<property name="address" column= "ADDRESS" type="string"/>
或者:
<property name="address" type="string">
<column name="ADDRESS" />
</property>
元素的子元素比column屬性提供更多的功能,它可以更加詳細(xì)地描述表的字段。例如,以下子元素指定CUSTOMERS表中的NAME字段的SQL類型為varchar(15),不允許為null,并且為這個(gè)字段建立了索引:
<property name="name" type="string">
<column name="NAME" sql-type="varchar(15)"
not-null="true" index="idx_name" />
</property>
子元素主要和hbm2ddl工具聯(lián)合使用。當(dāng)使用hbm2ddl工具來(lái)自動(dòng)生成數(shù)據(jù)庫(kù)Schema時(shí),hbm2ddl工具將依據(jù)子元素提供的信息來(lái)定義表的字段。關(guān)于hbm2ddl工具的用法參見本書第3章(hbm2java和hbm2ddl工具)。如果數(shù)據(jù)庫(kù)Schema是通過手工方式創(chuàng)建的,就不必通過子元素設(shè)定字段的詳細(xì)信息,通常只需設(shè)定它的name屬性和not-null屬性就可以了,例如:
<property name="name" type="string">
<column name="NAME" not-null="true" />
</property>
或者:
<property name="name" column="NAME"
type="string" not-null="true" />
除了not-null屬性以外,子元素的多數(shù)屬性(如sql-type或index屬性)都不會(huì)影響Hibernate的運(yùn)行時(shí)行為。
Hibernate采用XML文件來(lái)配置對(duì)象-關(guān)系映射,有以下優(yōu)點(diǎn):
1、Hibernate既不會(huì)滲透到上層域模型中,也不會(huì)滲透到下層數(shù)據(jù)模型中。
2、軟件開發(fā)人員可以獨(dú)立設(shè)計(jì)域模型,不必強(qiáng)迫遵守任何規(guī)范。
3、數(shù)據(jù)庫(kù)設(shè)計(jì)人員可以獨(dú)立設(shè)計(jì)數(shù)據(jù)模型,不必強(qiáng)迫遵守任何規(guī)范。
4、對(duì)象-關(guān)系映射不依賴于任何程序代碼,如果需要修改對(duì)象-關(guān)系映射,只需修改XML文件,不需要修改任何程序,提高了軟件的靈活性,并且使維護(hù)更加方便