UML软件工程组织

Java对象持久化技术之Hibernate入门之二
来源:赛迪网 作者:孙卫琴
创建数据库Schema 在本例中,与Customer类对应的数据库表名为CUSTOMERS,它在MySQL数据库中的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表有一个ID字段,它是表的主键,它和Customer类的id属性对应。CUSTOMERS表中的字段使用了各种各样的SQL类型,参见表2-2。

表2-2 CUSTOMERS表的字段使用的SQL类型
字 段 名 SQL类型 说 明
ID BIGINT 整数,占8字节,取值范围为:-2^63 ~ 2^63 - 1
NAME VARCHAR 变长字符串,占0 ~ 255字节
SEX CHAR 定长字符串,占0 ~ 255字节
IS_MARRIED BIT 布尔类型
DESCRIPTION TEXT 长文本数据,占0 ~ 65535字节。如果字符串长度小于255, 可以用VARCHAR或CHAR类型来表示。 如果字符串长度大于255, 可以定义为TEXT类型
(续表)
字 段 名 SQL类型 说 明
IMAGE BLOB 二进制长数据,占0 ~ 65 535字节, BLOB是Binary Large Object的缩写。在本例中, IMAGE字段用来存放图片数据
BIRTHDAY DATE 代表日期,格式为"YYYY-MM-DD"
REGISTERED_TIME TIMESTAMP 代表日期和时间,格式为"YYYYMMDDHHMMSS"

2.4 创建对象-关系映射文件

Hibernate采用XML格式的文件来指定对象和关系数据之间的映射。在运行时,Hibernate将根据这个映射文件来生成各种SQL语句。在本例中,将创建一个名为Customer.hbm.xml的文件,它用于把Customer类映射到CUSTOMERS表,这个文件应该和Customer.class文件存放在同一个目录下。例程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,文档类型定义),它对XML文件的语法和格式做了定义。Hibernate的XML解析器将根据DTD来核对XML文件的语法。

每一种XML文件都有独自的DTD文件。Hibernate的对象-关系映射文件使用的DTD文件的下载网址为:http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd。此外,在Hibernate软件包的src\net\sf\hibernate目录下也提供了hibernate-mapping-2.0.dtd文件。在这个文件中,描述顶层元素的代码如下:

<!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*))
)>


元素是对象-关系映射文件的根元素,其他元素(即以上DTD代码中括号以内的元素,如子元素)必须嵌入在元素以内。在 元素中又嵌套了好多子元素。

在以上DTD代码中,还使用了一系列的特殊符号来修饰元素,表2-3描述了这些符号的作用。在创建自己的对象-关系映射文件时,如果不熟悉某种元素的语法,可以参考DTD文件。

表2-3 DTD中特殊符号的作用

符 号 含 义
无符号 该子元素在父元素内必须存在且只能存在一次
+ 该子元素在父元素内必须存在, 可以存在一次或者多次
* 该子元素在父元素内可以不存在, 或者存在一次或者多次, 它是比较常用的符号
? 该子元素在父元素内可以不存在, 或者只存在一次,它是比较常用的符号

根据表2-3可以看出,在元素中和等子元素可以不存在,或者存在一次或者多次;在元素中,子元素必须存在且只能存在一次,元素可以不存在,或者存在一次或者多次。

此外,在映射文件中,父元素中的各种子元素的定义必须符合特定的顺序。例如,根据元素的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解析器在运行时会抛出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类。如果需要映射多个持久化类,那么既可以在同一个映射文件中映射所有类,也可以为每个类创建单独的映射文件,映射文件和类同名,扩展名为"hbm.xml"。后一种做法更值得推荐,因为在团队开发中,这有利于管理和维护映射文件。

元素指定类和表的映射,它的name属性设定类名,table属性设定表名。以下代码表明和Customer类对应的表为CUSTOMERS表:

<class name="mypack.Customer"
table="CUSTOMERS">

如果没有设置元素的table属性,Hibernate将直接以类名作为表名,也就是说,在默认情况下,与mypack.Customer类对应的表为Customer表。

元素包含一个 子元素及多个子元素。 子元素设定持久化类的OID和表的主键的映射。以下代码表明Customer类的id属性和CUSTOMERS表中的ID字段对应。

<id name="id" column="ID" type="long">
<generator class="increment"/>

</id>

元素的子元素指定对象标识符生成器,它负责为OID生成惟一标识符。本书第5章(映射对象标识符)详细介绍Hibernate提供的各种对象标识符生成器的用法。

子元素设定类的属性和表的字段的映射。 子元素主要包括name、type、column和not-null属性。

1. 元素的name属性

元素的name属性指定持久化类的属性的名字。

2. 元素的type属性

元素的type属性指定Hibernate映射类型。Hibernate映射类型是Java类型与SQL类型的桥梁。表2-4列出了Customer类的属性的Java类型、Hibernate映射类型,以及CUSTOMERS表的字段的SQL类型这三者之间的对应关系。

表2-4 Java类型、Hibernate映射类型以及SQL类型之间的对应关系

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类型,并且与此对应的CUSTOMERS表的字段为VARCHAR类型,那么应该把Hibernate映射类型设为string,例如:


<property name="name" 
column="NAME"  type="string" 
not-null="true" />

如果Customer类的属性为java.lang.String类型,并且与此对应的CUSTOMERS表的字段为TEXT类型,那么应该把Hibernate映射类型设为text,例如:


<property name="description" 
column="DESCRIPTION"  type="text"/>

如果Customer类的属性为byte[]类型,并且与此对应的CUSTOMERS表的字段为BLOB类型,那么应该把Hibernate映射类型设为binary,例如:


<property name="image"  column="IMAGE"
type="binary"/>

如果没有显式设定映射类型,Hibernate会运用Java反射机制先识别出持久化类的属性的Java类型,然后自动使用与之对应的默认的Hibernate映射类型。例如,Customer类的address属性为java.lang.String类型,与java.lang.String对应的默认的映射类型为string,因此以下两种设置方式是等价的:


<property name="address"  column="ADDRESS"/>

或者:


<property name="address"  type="string" />


对于Customer类的description属性,尽管它是java.lang.String类型,由于CUSTOMERS表的DESCRIPTION字段为text类型,因此必须显式地把映射类型设为text。

3. 元素的not-null属性

如果 元素的not-null属性为true,表明不允许为null,默认为false。例如以下代码表明不允许Customer类的name属性为null:

<property name="name"  column="NAME"
type="string"  not-null="true" />

Hibernate在持久化一个Customer对象时,会先检查它的name属性是否为null,如果为null,就会抛出以下异常:


net.sf.hibernate.PropertyValueException: 
not-null property references 
a null or transient value:
mypack.Customer.name

如果数据库中CUSTOMERS表的NAME字段不允许为null,但在映射文件中没有设置not-null属性:


<property name="name"  column="NAME"  
type="string"  />

那么Hibernate在持久化一个Customer对象时,不会先检查它的name属性是否为null,而是直接通过JDBC API向CUSTOMERS表插入相应的数据,由于CUSTOMERS表的NAME字段设置了not null约束,因此数据库会抛出错误:


708 ERROR JDBCExceptionReporter:
58 - General error,  message from server: 
"Column 'NAME' cannot be null"

值得注意的是,对于实际Java应用,当持久化一个Java对象时,不应该依赖Hibernate或数据库来负责数据验证。在四层应用结构中,应该由表述层或者业务逻辑层负责数据验证。例如对于Customer对象的name属性,事实上在表述层就能检查name属性是否为null,假如表述层、业务逻辑层和Hibernate持久化层都没有检查name属性是否为null,那么数据库层会监测到NAME字段违反了数据完整性约束,从而抛出异常,如图2-2所示,包含非法数据的Customer对象从表述层依次传到数据库层,随后从数据库层抛出的错误信息又依次传到表述层,这种做法显然会降低数据验证的效率。



图2-2 由数据库层负责数据验证


既然如此,把元素的not-null属性设为true,有何意义呢?这主要是便于在软件开发和测试阶段能捕获表述层或者业务逻辑层应该处理而未处理的异常,提醒开发人员在表述层或者业务逻辑层中加入必要的数据验证逻辑。

4.元素的column属性
元素的column属性指定与类的属性映射的表的字段名。以下代码表明和address属性对应的字段为ADDRESS字段:

<property name="address" column= "ADDRESS" 
type="string"/>

如果没有设置< property >元素的column属性,Hibernate将直接以类的属性名作为字段名,也就是说,在默认情况下,与Customer类的address属性对应的字段为address字段。

元素还可以包括子元素,它和元素的column属性一样,都可以设定与类的属性映射的表的字段名。以下两种设置方式是等价的:


<property name="address" column= "ADDRESS" 
type="string"/>

或者:


<property name="address"  
type="string">
<column  name="ADDRESS" />
</property>

元素的子元素比column属性提供更多的功能,它可以更加详细地描述表的字段。例如,以下子元素指定CUSTOMERS表中的NAME字段的SQL类型为varchar(15),不允许为null,并且为这个字段建立了索引:


<property name="name" type="string">
      <column name="NAME" sql-type="varchar(15)"
	  not-null="true"  index="idx_name" />
</property>

子元素主要和hbm2ddl工具联合使用。当使用hbm2ddl工具来自动生成数据库Schema时,hbm2ddl工具将依据子元素提供的信息来定义表的字段。关于hbm2ddl工具的用法参见本书第3章(hbm2java和hbm2ddl工具)。如果数据库Schema是通过手工方式创建的,就不必通过 子元素设定字段的详细信息,通常只需设定它的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属性以外,子元素的多数属性(如sql-type或index属性)都不会影响Hibernate的运行时行为。

图2-3显示了Customer.hbm.xml配置的对象-关系映射。



图2-3 Customer.hbm.xml配置的映射
Hibernate采用XML文件来配置对象-关系映射,有以下优点:

1、Hibernate既不会渗透到上层域模型中,也不会渗透到下层数据模型中。

2、软件开发人员可以独立设计域模型,不必强迫遵守任何规范。

3、数据库设计人员可以独立设计数据模型,不必强迫遵守任何规范。

4、对象-关系映射不依赖于任何程序代码,如果需要修改对象-关系映射,只需修改XML文件,不需要修改任何程序,提高了软件的灵活性,并且使维护更加方便。


 

 

版权所有:UML软件工程组织