OpenDoc Series’
Hibernate 开发指南
V1.0
作者:夏昕 xiaxin(at)gmail.com
So many open source projects. Why not Open your Doc uments? J
Hibernate Developer’ s Guide Version 1.0
文档说明
参与人员:
作者 联络
夏昕 xiaxin(at)gmail.com
(at) 为 email @ 符号
发布记录
版本 日期 作者 说明
0.9 2004.5.1 夏昕 第一版
1.0 2004.9.1 夏昕 错误修订
增加 Hibernate in Spring
OpenDoc 版权说明
本文档版权归原作者所有。
在免费、且无任何附加条件的前提下,可在网络媒体中自由传播。
如需部分或者全文引用,请事先征求作者意见。
如果本文对您有些许帮助,表达谢意的最好方式,是将您发现的问题和文档改进意见及时反馈给
作者。当然,倘若 有时间 和能力,能为技术群 体无偿贡献自己的所学为 最好的回 馈。
另外,笔者近来试图就日本、印度的 软件 开发 模式进行一些 调研。如果诸位可 以赠阅日本、印度
软件 研发 过程中的需求、 设计文档 以供研究, 感激不尽!
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
Hibernate 开发指南
本文是由笔 者 2003 年底 一个咨询项目 中,为客户做 的持久层设计培训
文案整理而来。
其中的内 容涉及 Hibernate 的使用,以及一部分笔者实际咨询 项目中的
经验积累, 另一方 面, 大部分是笔者在 Hibernate 的官方 论坛中 与众多
技术专家交流所 得。
既来于斯,则 归 于斯。希望 能聊有所用。
本文并非试图 替代 Hibernate Reference,相 对而言 ,Hibernate Reference
的编写目 的是为开 发者提供 更简便的条目 索引,而本文目 标则在 于为开
发人员 提供 一个入门 和掌握 Hibernate 的 途径 。
本文需结合 Hibernate Reference 使 用。
笔者好友曹晓钢义务组织了 Hibernate 文档的汉化工作,在此对其辛勤劳作致敬。
中文版 Hibernate Reference 将被包含在 Hibernate 下个官方 Release 中,目前可
通过 http://www.redsaga.com 获取中文版 Hibernate Reference 的最新版本。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
Hibernate 开发指南 .......................................................................................................3
准备工作 ..........................................................................................................5
构建 Hibernate 基础代码 ...............................................................................5
由数据库产生基础代码...........................................................................6
Hibernate 配置 ..............................................................................................17
第一段代码 ....................................................................................................19
Hibernate 基础语义 ......................................................................................21
Configuration ........................................................................................21
SessionFactory.......................................................................................22
Session....................................................................................................22
Hibernate 高级特性 ......................................................................................................24
XDoclet 与 Hibernate 映射 ...........................................................................24
数据检索 ........................................................................................................33
Criteria Query...............................................................................33
Criteria 查询表达式 ................................................................33
Criteria 高级特性 ....................................................................35
限定返回 的记录范围 .............................................................35
对查询结果进行排序.............................................................35
Hibernate Query Language (HQL) .........................................36
数据关联 ........................................................................................................37
一对一关联.............................................................................37
一对多关联.............................................................................39
Ø 单向一对 多关系 ......................................................39
Ø 双向一对 多关系 ......................................................44
多 对多关联 .............................................................................49
数据访问 ........................................................................................................56
PO 和 VO ...............................................................................................56
关于 unsaved-value ...............................................................................59
Inverse 和 Cascade .........................................................................61
延迟加载(Lazy Loading )............................................................61
事务 管理 ........................................................................................................65
基于 JDBC 的事 务管理.........................................................................66
基于 JTA 的事 务管理 ...........................................................................67
锁(locking ).........................................................................................70
悲观锁(Pessimistic Locking ) .......................................70
乐观锁(Optimistic Locking )..........................................71
Hibernate 分页 ..........................................................................................75
Cache 管理 ....................................................................................................77
Session 管理 ...............................................................................................81
Hibernate in Spring...........................................................................................86
编后赘言 ........................................................................................................................92
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
Hibernate 初识
首先来 看个 Quick Start 教程。
准备工作
1. 下载 Ant 软件包 , 解压缩(如 C:\ant\)。并将其 bin 目 录( 如 c:\ant\bin )添加 到系统
PATH 中。
2. 下载 Hibernate、 Hibernate-Extension 和 Middlegen-Hibernate 软件包 的最新 版本。
http://prdownloads.sourceforge.net/hibernate/
构建 Hibernate 基础代码
Hibernate 基础代码包括:
1. POJO
POJO 在 Hibernate 语义中理 解为 数据库表所对 应的 Domain Object。 这里的 POJO
就 是所谓的 “Plain Ordinary J ava O bject ”,字面 上 来 讲 就 是无 格式 普通 Java 对象, 简
单的可以理解为 一个不 包含逻辑代码的值对象(Value Object 简称 VO)。
一 个典型的 POJO:
public class TUser implements Serializable {
private String name;
public User(String name) {
this .name = name;
}
/** default constructor */
public User() {
}
public String getName() {
return this .name;
}
public void setName(String name) {
this .name = name;
}
}
2. Hibernate 映射文件
Hibernate 从 本 质上来 讲是一 种“对 象-关系型数据映射”( O bject R elational
M apping 简称 ORM )。前面的 POJO 在 这里 体现的 就是 ORM 中 Object 层的 语义 ,
而映射(Mapping )文件 则是将对 象(Object)与 关系型数据(Relational )相关联
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
的 纽带,在 Hibernate 中, 映射 文件 通常以 “ .hbm.xml”作为 后缀 。
构建 Hibernate 基础代码通常有以 下 途径:
1. 手工编写
2. 直接从数据库中 导出表 结构, 并生成对 应的 ORM 文件和 Java 代码。
这是实际开 发中最 常用的方式,也 是这里所推荐 的方式。
通过 直接从目 标数据库中 导出数据结构,最 小化了 手工编码和调整 的可能性 ,从而
最 大程度上保证了 ORM 文件和 Java 代码与实际 数据库结构相 一 致 。
3. 根据现有的 Java 代码生成对 应的 映射文件,将 Java 代码与 数据库表 相绑定。
通 过预先 编写 好的 POJO 生成 映射 文件,这种 方式在 实际开 发中 也经常 使 用, 特别
是结合了 xdoclet 之后 显得 尤为 灵活 ,其潜 在问题就 是与实际数据库结构之 间 可能
出 现的同步 上 的障碍,由 于 需 要 手工 调整 代码 ,往往调整的 过程 中由 于 手工操 作的
疏漏,导 致最后生成 的 配置文件错误,这点需 要在开 发中 特别注意。
结合 xdoclet,由 POJO 生成映射 文件的技术我们 将在 “高级特性” 章节中进行 探讨 。
由数据库产生基础代码
通 过 Hibernate 官 方提 供 的 MiddleGen for Hibernate 和 Hibernate_Extension 工 具包 ,我
们可以 很方便 的 根据现有 数据库, 导出数据库表 结构, 生成 ORM 和 POJO。
1) 首先,将 Middlegen-Hibernate 软件 包解压缩( 如解压缩到 C:\Middlegen\ )。
2) 配置目标数据库 参数
进入 MiddleGen 目录 下的\config\database 子目录, 根据 我们实际采 用的数据库打开
对应 的配置 文件。如这里我们用的是 mysql 数据库 ,对应 的就 是 mysql.xml 文件。
<property name="database.script.file"
value="${src.dir}/sql/${name}-mysql.sql"/>
<property name="database.driver.file"
value="${lib.dir}/mysql.jar"/>
<property name="database.driver.classpath"
value="${database.driver.file}"/>
<property name="database.driver"
value="org.gjt.mm.mysql.Driver"/>
<property name="database.url"
value="jdbc:mysql://localhost/sample "/>
<property name="database.userid"
value="user "/>
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
<property name="database.password"
value="mypass "/>
<property name="database.schema"
value=""/>
<property name="database.catalog"
value=""/>
<property name="jboss.datasource.mapping"
value="mySQL"/>
其中下划线标 准 的部分是我们 进行配置 的内容,分别 是数据 url 以及数据库用
户名和 密码。
3) 修改 Build.xml
修改 MiddleGen 根目录 下的 build.xml 文件,此 文件是 Middlegen-Hibernate 的 Ant
构建 配置。Middlegen-Hibernate 将根据 build.xml 文件中的具 体 参数生成数据库 表 映射
文件。可配置的 项目包括 :
a) 目标数据库配置文件 地址
查找关 键字 ”!ENTITY ”,得到 :
<!DOCTYPE project [
<!ENTITY database SYSTEM
"file:./config/database/hsqldb.xml">
]>
默认情况 下,MiddleGen 采 用的是 hsqldb.xml,将其 修改 为 我们 所用的数据
库配置文件(mysql.xml):
<!DOCTYPE project [
<!ENTITY database SYSTEM
"file:./config/database/mysql.xml">
]>
b) Application name
查找 :
<property name="name" value="airline"/>
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
“ aireline”是 MiddleGen 原始配置中默认 的 Application Name ,将 其 修改 为我们
所希望 的名称 ,如“HibernateSample”:
<property name="name" value="HibernateSample" />
c) 输出目 录
查找关 键字“name="build.gen-src.dir" ”,得到 :
<property name="build.gen-src.dir"
value="${build.dir}/gen-src" />
修改 value="${build.dir}/gen-src" 使其指向我们 所期 望的 输出目 录 ,
这里我们修改为 :
<property name="build.gen-src.dir"
value="C:\sample" />
d) 对应代码的 Package name
查找 关 键 字“destination ” ,得到 :
<hibernate
destination="${build.gen-src.dir}"
package="${name}.hibernate"
genXDocletTags="false"
genIntergratedCompositeKeys="false"
javaTypeMapper=
"middlegen.plugins.hibernate.HibernateJavaTypeMapper"
/>
可 以 看到,hibernate 节点 package 属性的 默认 设 置 实际 上 是由前 面 的
Application Name (${name}) 和“ .hibernate”组 合而 成,根 据 我们的需 要,
将 其 改 为 :
<hibernate
destination="${build.gen-src.dir}"
package="org.hibernate.sample"
genXDocletTags="true"
genIntergratedCompositeKeys="false"
javaTypeMapper=
"middlegen.plugins.hibernate.HibernateJavaTypeMapper"
/>
这里 还有一个 属性 genXDocletTags,如果设 置 为 true, 则生成 的 代码 将 包含
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
xdoclet tag ,这为以后在 开 发 过程 中借 助 xdoclet 进行 映射 调整 提 供 了帮助。关
于 Hibernate 的 xdoclet 使 用,请参 见“高级特性 ” 中的相关 内容。
注意,如果使 用的数据库为 SQLServer ,需 要将 build.xml 中如下部分( 下 划
线部分) 删除, 否则 Middlegen 会报出 找不到表的错误。
<middlegen
appname="${name}"
prefsdir="${src.dir}"
gui="${gui}"
databaseurl="${database.url}"
initialContextFactory="${java.naming.factory.initial}"
providerURL="${java.naming.provider.url}"
datasourceJNDIName="${datasource.jndi.name}"
driver="${database.driver}"
username="${database.userid}"
password="${database.password}"
schema="${database.schema}"
catalog="${database.catalog}"
>
至此 为 止 ,MiddleGen 已经 配置完毕 ,在 MiddleGen 根目录下 运 行 ant ,就 将 出 现
MiddleGen 的界面 :
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
①
②
④
⑤
⑥
⑧
⑨
可以看到,数据库 中的表结构已经 导入到 MiddleGen 的操 作界面 中,选定数据库
表视图 中的表元素 ,我们即 可调整各 个数据库 表的属性 。
1 Domain Class Name
对 应 POJO 的类名
2 Key Generator
主键产 生 器
可 选项 说明:
1) Assigned
⑦
③
⑩
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
主键 由外 部 程 序负 责生成,无需 Hibernate 参与。
2) hilo
通 过 hi/lo 算法实现的主键生成 机制 ,需要 额 外的 数据库 表保存 主
键生成历史状态。
3) seqhilo
与 hilo 类似,通过 hi/lo 算法实现的主键 生成机制, 只 是主键 历史
状态保 存在 Sequence 中,适用于 支持 Sequence 的 数据库,如 Oracle。
4) increment
主键 按数值 顺 序 递增。此方式的实 现 机制为 在 当 前 应 用 实例 中 维持
一个变量 ,以保存着 当前的最大值,之后每次需要生成 主键的时候
将此值 加 1 作为主键 。
这种方式可能产 生的问题是:如果当 前有多个实例访问同 一个数据
库,那么由于各 个实 例各自 维护主键 状态,不同 实例可 能生成同样
的 主键 , 从而造 成 主键重复异 常 。因此,如果同 一 数据库有多个实
例访问,此 方式 必须避免使 用。
5) identity
采 用数据库提供 的 主键 生成 机制。如 DB2、SQL Server 、MySQL
中的主键生成 机制 。
6) sequence
采 用 数据库提供 的 sequence 机制 生成 主键。如 Oralce 中的
Sequence。
7) native
由 Hibernate 根据底层数据库自行 判断采用 identity 、hilo、sequence
其中一种 作为主键生成 方式。
8) uuid.hex
由 Hibernate 基于 128 位唯 一 值产 生 算法 生成 16 进 制数值(编码后
以长 度 32 的字符串 表示) 作为主键 。
9) uuid.string
与 uuid.hex 类似, 只 是 生成的 主键未进 行 编码(长 度 16)。在某些
数据库中可能 出现问题(如 PostgreSQL)。
10) foreign
使 用外 部表的字段 作 为 主键 。
一般而言 ,利用 uuid.hex 方式生成主键 将提供 最好的性能 和数据库 平台适
应 性。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
另外由 于常用的 数据库,如 Oracle、 DB2、 SQLServer、 MySql 等 ,都 提
供了易用的 主键生成机制(Auto-Increase 字段 或者 Sequence)。我们可以在数
据库 提供的 主键 生成 机制 上 ,采用 generator-class=native 的 主键 生成方式。
不过值 得注意的是,一些数据库提供的 主键生成机制在 效率 上未必最 佳,
大量并发 insert 数据时可能会 引起 表之间 的 互锁。
数据库 提供 的 主键 生成 机制,往往是通过 在一 个内 部表中 保 存当前 主键 状
态(如对于 自增 型主键而 言,此内 部表中就维护着当前的最 大值和递增量 ),
之 后每次插 入数据 会 读取这个最大值 , 然后加 上 递增 量 作 为 新记录的主键 , 之
后再把 这个 新的最大 值更新回内 部表中,这样,一 次 Insert 操 作可能导致 数据
库内部 多次表 读 写操作,同时伴随的还 有 数据的加锁解锁 操作,这对性能 产 生
了 较大 影响 。
因此,对于并 发 Insert 要求较高的系统,推荐采用 uuid.hex 作为 主键生成
机制。
3 如果需要采 用 定制 的 主键产生算法 , 则在此处配置主键 生成 器,主键生成器必
须实现 net.sf.hibernate.id.IdentifierGenerator 接口。
4 Schema Name
数据库 Schema Name。
5 Persister
自定义持久类 实现 类类名。如果系统 中还需要 Hibernate 之外的持久层实
现机制 ,如通 过 存储过程得到 目标数据集,甚至从 LDAP 中 获取数据来 填
充 我们的 POJO。
6 Enable proxies
是 否使 用 代理 ( 用 于延迟加载[Lazy Loading])。
7 Dynamic Update
如果 选定 , 则生成 Update SQL 时不 包含 未 发 生变动 的 字段属性 , 这样 可
以在一定 程度上提升 SQL 执行效能。
8 Mutable
类 是否 可变 , 默认 为 选定 状态(可 变 )。如果不希望应用程序对此类 对应
的数据记 录进行 修改( 如对于数据库视图),则 可将取消其选定 状态 ,之
后 对此类的 Delete 和 Update 操作都 将 失效 。
9 Implement the Lifecyle interface
是 否实 现 Lifecyle 接口。Lifecyle 接口提供了 数据 固化过程中的 控制机制 ,
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
通 过实现 Lifecyle 接口, 我们 可 以 在数据库操作中加入回调 ( Call Back)
机制,如在数据库操 作之 前,之后触发 指定操作。
10 Implement the Validatable interface
是 否实 现 Validatable 接口。 通 过实现 Validatable 接口,我们可以 在 数据被
固化到数据库表 之前对其合 法性进行验证。
值 得注意的是, 通 过实现 Lifecyle 接口, 我们同样可以 在 数据 操作 之 前验
证数据合法 性,不 同的是,Validatable 接口中定 义的 validate 方法 可能会
被 调用多 次,因 此设计中应避 免在 Validatable 接口的 validate 方法实 现中
加入业务 逻辑的验证 。
以 上是针 对 Class 的设置 , 同样 ,在 MiddleGen 中, 我们也 可 以设 定字段 属性 。在
MiddleGen 中选定某个字 段, 界面下方 即出现 字段设 置栏:
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
①
②
③
④ ⑤
在 这里我们可以设 置 字 段的属性,其 中:
1 Hibernate mapping specialty
映射 类 型 :
Key :主键
Property :属性
Version :用于实 现 optimistic locking,参 见“高级特性 ”章节中关
于 optimistic locking 的描述
2 Java property name
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
字 段对 应 的 Java 属性名
3 Java Type
字 段对 应 的 Java 数据 类 型
4 Column updateable
生成 Update SQL 时是否 包含 本 字 段。
5 Column insertable
生成 Insert SQL 时是否 包含本 字段。
单击窗口 顶部的 Generate 按钮, MiddleGen 即为 我们 生成这些 数据库表所对 应的
Hibernate 映射文件。在 MiddleGen 根目录下的 \build\gen-src\net\hibernate\sample 目录中,
我们可以看到 对应 的以.hbm.xml 作为 后缀 的多个映射 文件,每个映射 文件都 对应 了数
据库的一个 表。
仅 有映射 文件 还不够 , 我们还 需 要根据这 些文件 生成 对 应 的 POJO。
POJO 的生成工作可以通过 Hibernate Extension 来完成, Hibernate Extension 的
tools\bin 目录下 包含三个 工具:
1. hbm2java.bat
根 据映射文件生成 对应 的 POJO。通过 MiddleGen 我们已经得到 了 映射 文件,
下一步就 是通过 hbm2java.bat 工具 生成 对应 的 POJO。
2. class2hbm.bat
根 据 POJO class 生成映射 文件,这个 工 具很少 用 到 , 这里也就不再详细介绍 。
3. ddl2hbm.bat
由数据库导出库表结构,并生成映射 文件以 及 POJO。这个功 能与 MiddleGen
的功能重 叠 ,但由于目前还 不够成 熟( 实际上已 经被废弃,不再维护),提供
的功能也 有限 ,所 以 我们还 是采 用 MiddleGen 生成映射 文件,之后由 hbm2java
根 据映射 文件生成 POJO 的方 式。
为了 使用 以上工具, 首先我们 需要配置 一些参 数,打开 tools\bin\setenv.bat 文件,修改
其 中的 JDBC_DRIVER 和 HIBERNATE_HOME 环境变量 , 使其指向我们的 实际 JDBC Driver
文件和 Hibernate 所在目录 ,如
set JDBC_DRIVER=c:\mysql\mysql.jar
set HIBERNATE_HOME=c:\hibernate
同 时检 查 一下环境 变量 CP 中的 各个项目 中是 否实际存 在, 特别是%CORELIB% 下的 jar
文件,某 些版本的发行包 中,默认配置中 的文件名与实际 的文件名 有所出入(如
%CORELIB%\commons-logging.jar, 在 Hibernate 发 行 包 中,可能实际 的文件名 是
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
commons-logging-1.0.3.jar ,诸如 此类)。
使用 hbm2java,根据 MiddleGen 生成 的映射 文件生成 Java 代码 :
打 开 Command Window,在 tools\bin 目录下执 行 :
hbm2java c:\sample\org\hibernate\sample\*.xml --output=c:\sample\
即 可生成对 应 的 POJO。 生成 的 POJO 保存在 我们指定 的 输 出目 录 下 ( c:\sample )。
目前 为止, 我们已经 完成 了通过 MiddleGen 产生 Hibernate 基础代码的 工作。配置
MiddleGen 也许并不是一件轻松 的事情 ,对于 Eclipse 的用户而言 ,目 前已经出 现了 好几个
Hibernate 的 Plugin,通过这些 Plugin 我们可以更 加轻松 的 完 成上述 工作,具 体的使 用方式
请 参 见附 录 。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
Hibernate 配置
前面已经得到了 映射文件和 POJO,为了 使 Hibernate 能真正运 作起来 ,我们还需 要一
个配置文件。
Hibernate 同时支持 xml 格式的配置 文件, 以及传统的 properties 文件 配置 方式, 不过 这
里建议采用 xml 型 配置文件。xml 配置文件提供 了更易读的 结构和 更强的 配置能力,可 以 直
接 对 映射文件加以 配置 ,而在 properties 文件中则无法配置 ,必须通过 代码中的 Hard Coding
加 载相应 的 映射 文件。下 面 如果 不 作 特别说明, 都指 的是 基于 xml 格 式文件的 配置方式。
配置文件 名默认为“hibernate.cfg.xml ”( 或者 hibernate.properties),Hibernate 初始化期
间 会自动 在 CLASSPATH 中寻找这个 文 件, 并读取其中的配置信息,为后期 数据库 操作 做 好
准备。
配置文件应 部署 在 CLASSPATH 中,对于 Web 应用而 言 , 配置文件应放置 在在
\WEB-INF\classes 目录下。
一个典型 的 hibernate.cfg.xml 配置 文件如下:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.
dtd">
<hibernate-configuration >
<!— - SessionFactory 配置 -->
<session-factory>
<!— - 数据库URL -->
< property name="hibernate.connection.url">
jdbc:mysql://localhost/sample
</property >
<!— - 数据库JDBC驱动 -->
< property name="hibernate.connection.driver_class">
org.gjt.mm.mysql.Driver
</property >
<!— - 数据库用户名 -->
< property name="hibernate.connection.username">
User
</property >
<!— - 数据库用户密码 -->
< property name="hibernate.connection.password">
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
Mypass
</property >
<!--dialect ,每个数据库都有其对应 的Dialet以匹配 其平台 特性 -->
<property name="dialect">
net.sf.hibernate.dialect.MySQLDialect
</property >
<!— - 是否 将运行 期生成的 SQL 输出到日志以供调试 -->
<property name="hibernate.show_sql">
True
</property >
<!— - 是否使 用数据库外连 接 -->
< property name="hibernate.use_outer_join">
True
</property >
<!— - 事务管理类型 ,这里我们使用JDBC Transaction -->
<property name="hibernate.transaction.factory_class">
net.sf.hibernate.transaction.JDBCTransactionFactory
</property >
<!—映射文件配置 ,注 意配置 文件名必须包含其 相对于根 的全路径 -->
<mapping resource="net/xiaxin/xdoclet/TUser.hbm.xml"/>
<mapping resource="net/xiaxin/xdoclet/TGroup.hbm.xml"/>
</session-factory >
</hibernate-configuration >
一个典型的 hibernate.properties 配置 文件如下:
hibernate.dialect net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class org.gjt.mm.mysql.Driver
hibernate.connection.driver_class com.mysql.jdbc.Driver
hibernate.connection.url jdbc:mysql:///sample
hibernate.connection.username user
hibernate.connection.password mypass
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
第一段代码
上 面我们已 经 完 成 了 Hiberante 的 基础代码 ,现在先从 一段 最 简单 的代码入手 ,感受 一
下 Hibernate 所提供 的强大 功能 。
下面这段 代码 是一个 JUnit TestCase,演示了 TUser 对象 的保存 和 读取 。考虑到读 者可
能 没有 JUnit 的使 用 经验 ,代码 中加 入了 一些 JUnit 相关注释。
public class HibernateTest extends TestCase {
Session session = null ;
/**
* JUnit中 setUp方法 在 TestCase初始化的时候会 自动调 用
* 一 般 用于初始化公用 资源
* 此例中,用于初始化Hibernate Session
*/
protected void setUp(){
try {
/**
* 采用 hibernate.properties配置文件的初 始化代码:
* Configuration config = new Configuration();
* config.addClass(TUser.class);
*/
//采用 hibernate.cfg.xml配置文件
//请注 意初始化 Configuration时的差异 :
// 1.Configuration的初始化方式
// 2.xml文件中已经 定义了Mapping文件,因此 无需再Hard Coding导入
// POJO文件的定义
Configuration config = new Configuration().configure();
SessionFactory sessionFactory =
config.buildSessionFactory();
session = sessionFactory.openSession();
} catch (HibernateException e) {
e.printStackTrace();
}
}
/**
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
* 与 setUp方法相 对应 , JUnit TestCase执行完毕时,会自动调 用tearDown方法
* 一般用于资源释放
* 此例中,用于关闭 在setUp方法 中打开 的 Hibernate Session
*/
protected void tearDown(){
try {
session.close();
} catch (HibernateException e) {
e.printStackTrace();
}
}
/**
* 对象持久化( Insert)测试 方法
*
* JUnit中,以” test”作为 前缀 的方法为测试方法 ,将被JUnit自动添 加
* 到测试计划中 运 行
*/
public void testInsert(){
try {
TUser user = new TUser();
user.setName("Emma" );
session.save(user);
session.flush();
} catch (HibernateException e) {
e.printStackTrace();
Assert.fail(e.getMessage());
}
}
/**
* 对象读取( Select)测试
* 请保证运行 之前数据库 中 已经存在 name=’ Erica’的记录
*/
public void testSelect(){
String hql=
" from TUser where name='Erica'";
try {
List userList = session.find(hql);
TUser user =(TUser)userList.get(0);
Assert.assertEquals(user.getName(),"Erica" );
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
} catch (HibernateException e) {
e.printStackTrace();
Assert.fail(e.getMessage());
}
}
}
主 流 IDE,如 Eclipse 、Intellij IDEA 和 JBuilder 中都内 置了 JUnit 支持 。下面 是 Eclipse
中运行该代码的结果(在 Run 菜 单中选择 Run as -> JUnit Test 即 可):
现在我们已经成功 实 现了 一个简单的 TUser 实例的保 存和读取 。可 以 看到,程序中通 过
少 量代码实现了 Java 对象 和 数据库数据 的同步, 同时 借 助 Hibernate 的有 力支 持 , 轻松实现
了对象到关系型数据库的 映射 。
相 对传统 的 JDBC 数据 访问模 式, 这样的实 现无 疑 更 符 合 面向对 象 的思想 , 同时也 大大
提高了开 发效率 。
上 面的 代码 中引 入 了几个 Hibernate 基础语义 :
1. Configuration
2. SessionFactory
3. Session
下 面我们就这几 个 关 键 概念进行 探讨 。
Hibernate 基础语义
Configuration
正如其名 , Configuration 类负责管理 Hibernate 的配置信息。 Hibernate 运行时需要
获取一些底层实 现的基 本信息 ,其 中几个关键属性包括:
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
1. 数据库 URL
2. 数据库用户
3. 数据库用户密 码
4. 数据库 JDBC 驱动类
5. 数据库 dialect,用于 对特定数据库提供支 持 ,其 中 包含了针对 特定 数据库 特性
的 实现,如 Hibernate 数据类 型到 特定 数据库数据 类 型 的 映射等 。
使用 Hibernate 必须首 先提 供这些 基础信息 以完成初 始化工作,为后继操作 做好准
备 。这些属性在 hibernate 配置文件 ( hibernate.cfg.xml 或 hibernate.properties ) 中加以设
定( 参见前面“Hibernate 配置”中的 示例 配置文件内容)。
当 我们调用:
Configuration config = new Configuration().configure();
时,Hibernate 会自动在 当 前的 CLASSPATH 中搜寻 hibernate.cfg.xml 文件并 将 其读
取到内 存中作为后继操 作的基础 配置。Configuration 类一 般只有在 获取 SessionFactory
时需要涉 及,当 获取 SessionFactory 之后,由于配置信息 已 经 由 Hibernate 维护并绑定
在返回 的 SessionFactory 之上,因 此一般 情况 下 无需再 对其 进行操作。
我们也可以 指定配置文件 名,如果不 希望使 用默认的 hibernate.cfg.xml 文件作为 配
置文件的话 :
File file = new File("c:\\sample\\myhibernate.xml" );
Configuration config = new Configuration().configure(file);
SessionFactory
SessionFactory 负责 创 建 Session 实例 。我们可以通 过 Configuation 实 例构建
SessionFactory:
Configuration config = new Configuration().configure();
SessionFactory sessionFactory = config.buildSessionFactory();
Configuration 实例 config 会根据当前的 配置信息,构造 SessionFactory 实例并返 回。
SessionFactory 一旦构造完毕, 即被 赋予特定的 配置信息。 也就是说, 之后 config 的任
何 变更 将 不会影响到 已 经 创建的 SessionFactory 实例(sessionFactory ) 。如果需要
使用 基 于 改动 后 的 config 实例的 SessionFactory ,需要从 config 重 新构建 一个
SessionFactory 实例。
Session
Session 是持久层操 作的基础, 相当于 JDBC 中的 Connection。
Session 实例通 过 SessionFactory 实例构建:
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
Configuration config = new Configuration().configure();
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
之后我们就可 以调用 Session 所提 供的 save、 find、flush 等方 法完成 持久层 操作:
Find:
String hql= " from TUser where name='Erica'" ;
List userList = session.find(hql);
Save:
TUser user = new TUser();
user.setName("Emma" );
session.save(user);
session.flush();
最 后调用Session.flush方法强 制 数据库同步,这里即强制Hibernate将 user 实
例立即同步到数据库中。如果在事务中则不 需要flush方法, 在事务提交 的时候,
hibernate自动会 执行 flush方法,另外当Session关闭时,也会自动执行flush方法。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
Hibernate 高级特性
XDoclet 与 Hibernate 映射
在 POJO 中融合 XDoclet 的映射文件自动 生成 机制,提供了除手动 编码 和由 数据库导出
基础代码的第三种选 择 。
本 章 将 结合 XDoclet 对 Hibernate 中的 数据映射 进行 介绍 。
实际开发中,往往首 先 使用 MiddleGen 和 hbm2java 工具 生成带 有 XDoclet tag 的 POJO
( MiddleGen build.xml中的 genXDocletTags 选项决 定了 是 否 在 映射 文件中生成XDoclet Tag,
详见 Hibernate Quick Start章节中关于 MiddleGen 的说明)。之后通过修改 POJO 中的 XDoclet
tag 进行映射关系 调整。
XDoclet 已经广泛运用在 EJB 开 发中,在 其 最 新 版本里 ,包含了一个为 Hibernate 提供支
持 的子类库 Hibernate Doclet,其 中 包含 了 生成 Hibernate 映射 文件所需的 ant 构建 支持以及
java doc tag 支持。
XDoclet 实现基 本原理 是,通过在 Java 代码加 入特定的 JavaDoc tag,从而为其添加特定
的附加语义 ,之后通过 XDoclet 工具对 代码 中 JavaDoc Tag 进行 分析,自动生成与 代码 对应
的配置 文件,XDoclet。
在 Hibernate-Doclet 中,通过引 入 Hibernate 相关的 JavaDoc tag,我们就可以由代码生成
对应 的 Hibernate 映射文 件。
下面 是一个代码片断 ,演示了 Hibernate-Doclet 的使 用方式:
/**
* @hibernate.class
* table="TUser"
*/
public class TUser implements Serializable {
……
/**
* @hibernate.property
* column="name"
* length="50"
* not- null="true"
*
* @return String
*/
public String getName() {
return this .name;
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
}
……
}
以 上是 使 用 Hibernate-Doclet 描述 POJO ( TUser )及其 对 应 表( TUser )之间 映射关系
的一个例子 。
其 中用到了两 个 hibernate doclet tag,@hibernate.class 和 @hibernate.property 。
这两个 tag 分别 描述了 POJO 所对应的数据库表 信息,以及 其字段对 应的 库表 字段信息。
之 后 Hibernate Doclet 就 会根 据这些信息 生成映射 文件:
<hibernate-mapping>
<class
name="net.xiaxin.xdoclet.TUser"
table="TUser"
>
<property
name="name"
type="java.lang.String"
column="name"
not-null="true"
length="50"
>
</class >
</hibernate-mapping >
这样我们只需 要维护 Java 代码,而 无需 再手动编写 具体的映射文件即 可完成 Hibernate
基础代码。
熟 记 Hibernate-Doclet 众多的 Tag,显 然不 是件 轻松 的事情 ,好在 目前的 主流 IDE 都提
供了 Live Template 支持。我们只需进行一些 配置工作,就可 以实现 Hibernate-Doclet Tag
的自动补 全功能 ,从而避 免了手工编写过程中可能 出现的问题。
附录 中提供了主 流 IDE, 包括 JBuilder,Intellij IDEA,Eclipse 的 Hibernate-Doclet 集成
指 南。
下面我们就 Hibernate Doclet 中常 用的 Tag 进行 探讨 ,关于 Tag 的 详细参 考 ,请参 见
XDoclet 的官 方指南( http://xdoclet.sourceforge.net/xdoclet/tags/hibernate-tags.html)以及
Hibernate Reference( http://www.hibernate.org)。
常 用 Hibernate-Doclet Tag 介绍:
1. Class 层面:
1) @hibernate.class
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
描述 POJO 与 数据库表之间 的 映射关系, 并指定 相关的运行 参 数。
参数 描述 类型 必须
table
dynamic-update
dynamic-insert
Proxy
discriminator-value
where
类对应 的表名
默认值:当 前 类名
生成 Update SQL 时,仅 包含 发生 变动
的字段
默认 值: false
生成 Insert SQL 时,仅 包含非空(null)
字段
默认 值:false
代理类
默认值:空
子类辨 别标识,用 于多态支 持。
数据 甄选条件,如果只 需 要 处理库表中 某
些特定数据 的时候 ,可 通过 此选项设定结
果集限定 条件。
如用户 表中保存 了 全国 所有用户 的数据,
而 我们的系统只 是 面 向 上海用户,则可指
定 where=” location=’ Shanghai’ "
Text N
Bool N
Bool N
Text N
Text N
Text N
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
典型场景:
/**
* @hibernate.class
* table="TUser" (1)
* dynamic- update="true" (2)
* dynamic- insert="true" (3)
* proxy=”” (4)
* discriminator-value=” 1” (5)
*/
public class TUser implements Serializable {
……
}
本 例 中 :
1 table 参数 指定了当前 类( TUser)对 应数据库表 “ TUser”。
2 dynamic- update 参数设 定为生成 Update SQL 时候 ,只包括当前发 生变化的
字 段 ( 提高 DB Update 性能)。
3 Dynamic-insert 参数设 定为生成 Insert SQL 时候 ,只包括当前 非空 字段。
( 提高 DB Insert 性能)
4 Proxy 参数为空,表明当 前类不使 用代理 ( Proxy)。 代理类的作用是为 Lazy
Loading 提供支 持 ,请参 见下面关 于 Lazy Loading 的有关内容。
5 discriminator-value 参数设为”1”。
discriminator-value 参 数 的 目 的是对多态 提供支 持 。请参 见下面关 于
@hibernate.discriminator 的说明。
2) @hibernate.discriminator
@hibernate.discriminator(识别器) 用于 提 供多态支持。
参数 描述 类型 必须
column
type
length
用 于区 分 各子类的字段名 称 。
默认值:当 前 类名
对 应的 Hibernate 类型
字段长度
如:
TUser类对应数据库表 TUser,并 且 User类有两个 派生 类 SysAdmin、
SysOperator。
在 TUser表中, 根据 user_type 字段区 分用 户类型。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
text Y
Bool N
Bool N
Hibernate Developer’ s Guide Version 1.0
为 了让Hibernate根据 user_type 能自动识别 对 应的 Class 类型(如 user_type==1
则自动 映射到SysAdmin类,user_type==2 则自动 映射到SysOperator类),我们需 要
在 映射 文件中进 行配置,而 在 Hibernate-Doclet 中,对 应 的 就 是
@hibernate.discriminator 标识和 @hibernate.class 以及 @hibernate.subclass 的
discriminator-value属性。
典型场景:
/**
*
* @hibernate.class
* table="TUser"
* dynamic- update="true"
* dynamic- insert="true"
*
* @hibernate.discriminator column="user_type" type="integer"
*/
public class TUser implements Serializable {
……
}
根类 TUser 中,通过 @hibernate.discriminator 指定了以 "user_type" 字段
作为识别字段 。
/**
* @hibernate.subclass
* discriminator- value="1"
*/
public class SysAdmin extends TUser {
……
}
/**
* @hibernate.subclass
* discriminator- value="2"
*/
public class SysOperator extends TUser {
……
}
SysAdmin 和 SysOperator 均继承自 TUser ,其 discriminator- value 分 别设置
为"1" 和"2" ,运 行期 Hibernate 在读取 t_user 表 数据时,会根 据其 user_type 字段进行
判断 ,如果是 1 的 话 则映射到 SysAdmin 类 ,如果是 2 映射到 SysOperator 类。
上 例 中,描述 SysAdmin 和 SysOperator 时,我们 引入了 一个 Tag :
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
@hibernate.subclass,顾名 思义,@hibernate.subclass 与 @hibernate.class
不 同之处就在 于 , @hibernate.subclass 描述的是一个子类 , 实际上 , 这 两 个 Tag
除去名称不同外,并没有什么区别。
2. Method 层面:
1) @hibernate.id
描述 POJO 中关键 字 段与数据库表主键之间 的 映射关系 。
参数 描述 类型 必须
column
type
length
unsaved-value
generator-class
主键字段 名
默认值:当 前 类名
字 段类 型 。
Hibernate 总是使 用对象型数据类 型作
为 字段类 型 ,如 int 对应 Integer , 因此
这里将 id 设为 基本类 型[如 int]以避 免对
象创 建的开销 的思路 是没有实际意义 的,
即 使这里 设 置 为基本 类 型,Hibernate内
部还 是会使用对象型数据对其 进行处 理 ,
只 是返回数据 的时 候再 转换为基本类 型
而已。
字 段长度
用 于对 象 是 否已经保 存的判定值 。
详 见“数据访问 ” 章节 的 相关讨论 。
主键产生方式 (详见 Hibernate Quick
Start 中关于 MiddleGen 的相关 说明)
取 值可为 下 列 值 中的任意一个:
Text N
Text N
Text N
Text N
Text Y
assigned
hilo
seqhilo
increment
identity
sequence
native
uuid.hex
uuid.string
foreign
2) @hibernate.property
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
描述 POJO 中 属性与数据库表 字段 之间的映射关系。
参数 描述 类型 必须
column
type
length
not-null
unique
insert
update
数据库 表 字 段名
默认值:当 前 类名
字段类 型
字段长度
字段是否允许为空
字段是否 唯一 (是否允许重复值)
Insert 操作时是 否 包含本 字 段数据
默认:true
Update 操作时是否包含 本 字段 数据
默认:true
典型 场景:
/**
* @hibernate.property
* column="name"
* length="50"
* not- null="true"
*
* @return String
*/
public String getName() {
return this .name;
}
Text N
Text N
Text N
Bool N
Bool N
Bool N
Bool N
注意:在 编写代码的 时候请,对将POJO 的getter/setter 方 法设定为public , 如果
设定为private ,Hibernate将无法对 属性的 存取 进行优化,只能转而采用传统的 反射 机制
进行操作, 这将 导致 大量的性 能开 销(特 别是在1.4 之前的Sun JDK 版本 以及IBM JDK 中,
反射所带来 的 系统开 销相当可 观)。
包含 XDoclet Tag的 代码 必须由xdoclet程序进 行 处 理以 生成 对 应 的映射文件,
xdoclet的处理模块可通 过 ant进行 加载 ,下面 是一个简单的 hibernate xdoclet的 ant
构建 脚本 ( 注意实际使用时需要根据实际 情况 对路径 和 CLASSPATH设定进 行调整 ) :
<?xml version="1.0"?>
<project name="Hibernate" default="hibernate" basedir="." >
<property name="xdoclet.lib.home"
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
value="C:\xdoclet-1.2.1\lib" />
<target name="hibernate" depends=""
description="Generates Hibernate class descriptor files." >
<taskdef name="hibernatedoclet"
classname="xdoclet.modules.hibernate.HibernateDocletTask" >
<classpath>
<fileset dir="${xdoclet.lib.home}" >
<include name="*.jar" />
</fileset>
</classpath>
</taskdef>
<hibernatedoclet
destdir="./src/"
excludedtags="@version,@author,@todo"
force="true"
verbose="true"
mergedir="." >
<fileset dir="./src/" >
<include name="**/hibernate/sample/*.java" />
</fileset>
<hibernate version="2.0" />
</hibernatedoclet>
</target>
</project>
除了上面 我们介绍的 Hibernate Doclet Tag,其他还 有:
Class层面;
@hibernate.cache
@hibernate.jcs-cache
@hibernate.joined-subclass
@hibernate.joined-subclass-key
@hibernate.query
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
Method层面
@hibernate.array
@hibernate.bag
@hibernate.collection-cache
@hibernate.collection-composite-element
@hibernate.collection-element
@hibernate.collection-index
@hibernate.collection-jcs-cache
@hibernate.collection-key
@hibernate.collection-key-column
@hibernate.collection-many-to-many
@hibernate.collection-one-to-many
@hibernate.column
@hibernate.component
@hibernate.generator-param
@hibernate.index-many-to-many
@hibernate.list
@hibernate.many-to-one
@hibernate.map
@hibernate.one-to-one
@hibernate.primitive-array
@hibernate.set
@hibernate.timestamp
@hibernate.version
具体的 Tag描述请参 见 XDoclet官方网站 提供 的 Tag说明 1。下 面 的 Hibernate高级特性介
绍 中, 我们也将涉 及 到这些Tag的实际使用。
1
http://xdoclet.sourceforge.net/xdoclet/tags/hibernate-tags.html
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
数据检索
数据 查询与检索是 Hibernate 中的一 个亮 点 。相对其他 ORM 实现而 言,Hibernate
提供了灵活多样的 查询 机制。其 中包括:
1. Criteria Query
2. H ibernate Q uery L anguage (HQL)
3. SQL
Criteria Query
Criteria Query 通过面向对象 化 的 设计 ,将 数据 查询条件封装为 一 个 对 象。 简单来
讲,Criteria Query 可以看 作是传 统 SQL 的对 象化表示 ,如:
Criteria criteria = session.createCriteria(TUser.class );
criteria.add(Expression.eq("name" ,"Erica" ));
criteria.add(Expression.eq("sex" ,new Integer(1)));
这里 的 criteria 实 例 实际 上 是 SQL “ Select * from t_user where
name=’ Erica’ and sex=1”的封装(我们可以 打开 Hibernate 的 show_sql 选项,
以 观察 Hibernate 在运 行 期生成的 SQL 语句 ) 。
Hibernate 在运行 期会根据 Criteria 中指定的 查询条件( 也就是上 面代码中 通 过
criteria.add 方法添 加的查询 表达式)生成相应的 SQL 语句。
这种 方式的特点 是 比较符合 Java 程序员的 编码 习惯, 并 且具备清晰 的可 读性 。 正因
为此, 不少 ORM 实现中都 提供 了类似的 实现机制( 如 Apache OJB)。
对于 Hibernate 的初学 者,特别是对 SQL 了解有限 的程 序员而 言,Criteria Query
无疑 是上手的极佳 途径,相对 HQL ,Criteria Query 提供了更易 于理解的查 询手段,借
助 IDE 的 Coding Assist 机制 ,Criteria 的使 用几乎不 用太多 的学习 。
Criteria 查询表达式
Criteria 本身只 是一 个查 询容 器 , 具 体的 查询 条件需 要通过 Criteria.add
方法添 加到 Criteria 实例 中。
如前例 所示 ,Expression 对 象具 体 描述了查询 条件。针 对 SQL 语 法 ,
Expression 提供了 对 应 的 查询限 定 机制,包括 :
方法 描述
Expression.eq
Expression.allEq
Expression.gt
对应 SQL“ field = value” 表达式。
如 Expression.eq("name", "Erica" )
参 数为一 个 Map 对象, 其中包含了多个 属性 -值 对
应关系。相当于多个 Expression.eq 关系的叠 加。
对应 SQL 中的 “ field > value ” 表达式
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
Expression.ge
Expression.lt
Expression.le
Expression.between
Expression.like
Expression.in
Expression.eqProperty
对应 SQL 中的 “ field >= value” 表达式
对应 SQL 中的 “ field < value” 表达式
对应 SQL 中的 “ field <= value” 表达式
对应 SQL 中的 “ between” 表达式
如下面 的表达式表示年龄(age)位于 13到 50 区
间内 。
Expression.between("age" ,new
Integer(13),new Integer(50));
对应 SQL 中的 “ field like value” 表达式
对应 SQL 中的 ” field in …” 表达式
用于比较两 个属性之间的值 ,对 应 SQL 中的“field
= field”。
如:
Expression.eqProperty(
"TUser.groupID",
"TGroup.id"
Expression.gtProperty
Expression.geProperty
Expression.ltProperty
Expression.leProperty
Expression.and
);
用 于比较两 个 属性之间的值 ,对 应 SQL 中的 “field
> field”。
用 于比较两 个 属性之间的值 ,对 应 SQL 中的 “field
>= field”。
用 于比较两 个 属性之间的值 ,对 应 SQL 中的 “field
< field”。
用 于比较两 个 属性之间的值 ,对 应 SQL 中的 “field
<= field”。
and 关系组 合。
如:
Expression.and(
Expression.eq("name" ,"Erica" ),
Expression.eq(
"sex",
new Integer(1)
)
);
Expression.or
or 关系组 合。
如:
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
Expression.or(
Expression.eq("name" ,"Erica" ),
Expression.eq("name" ,"Emma" )
);
Expression.sql
注 意 Expression 各方法 中的 属性名参 数(如 Express.eq 中的第一个 参 数),这里
所谓属性名 是 POJO 中对应实际 库 表字段 的 属性名(大小写敏 感), 而非库表中的实
际 字段名 称 。
作为补充,本方法 提供了 原生 SQL 语法的支持。我
们 可以通 过 这 个 方 法直接通过 SQL 语 句限 定查询
条件。
下面 的代码返回 所有名称以 “Erica”起始的记录:
Expression.sql(
“ lower({alias}.name) like lower(?)” ,
"Erica%",
Hibernate.STRING
);
其 中的“ {alias}”将由 Hibernate 在运 行 期使
用当 前关 联的 POJO 别名替换。
Criteria 高级特性
限定返回 的记录 范围
通 过 criteria. setFirstResult/setMaxResults 方法 可 以限制 一 次查询返 回
的记录范围:
对查询结果进行排序
//查询所有 groupId=2的记录
//并分别按照姓名(顺 序)和 groupId(逆序)排序
Criteria criteria = session.createCriteria(TUser.class );
criteria.add(Expression.eq("groupId" ,new Integer(2)));
criteria.addOrder(Order.asc("name" ));
Criteria criteria = session.createCriteria(TUser.class );
//限定查询返 回检索结果中, 从第一百 条结 果开始 的20 条记录
criteria.setFirstResult(100);
criteria.setMaxResults(20);
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
criteria.addOrder(Order.desc("groupId" ));
Criteria 作为 一种 对象化 的查询封装模 式,不过由 于 Hibernate 在实 现 过程中将精 力
更 加集 中在 HQL 查询 语言上 ,因此 Criteria 的功 能实现 还 没做到 尽 善 尽 美(这点 上 ,OJB
的 Criteria 实 现倒 是值得借鉴 ) ,因此 ,在实际开 发中,建议还 是采 用 Hibernate 官
方推荐的查询封装模式: HQL 。
Hibernate Query Language (HQL)
Criteria 提供了更 加 符合面向 对 象编程模式的查 询 封装模 式。 不过,HQL(Hibernate
Query Language)提供了 更加强大 的功能,在官 方开 发手册 中,也 将 HQL 作 为推荐的 查询
模式。
相 对 Criteria,HQL 提供了更接近传 统 SQL 语句的查询 语 法,也提供 了 更全面 的 特性 。
最简单 的一个例子 :
String hql = "from org.hibernate.sample.TUser" ;
Query query = session.createQuery(hql);
List userList = query.list();
上 面的 代码 将取 出 TUser 的所有对应记 录 。
如果我们 需要取出 名为“Erica”的用户 的记录 ,类似 SQL, 我们可以 通过 SQL 语句加
以 限定:
String hql =
"from org.hibernate.sample.TUser as user where user.name='Erica'";
Query query = session.createQuery(hql);
List userList = query.list();
其 中我们新引 入 了 两个 子 句“as”和“where ” ,as 子句为 类名 创建 了 一个 别名 ,而 where
子句 指定了限 定条件。
HQL
子句本身大小写无关,但是其中出现的类名和属性名必须注意大小写区分。
关 于 HQL, Hibernate 官方 开 发 手 册 中 已 经 提 供 了 极 其 详 尽 的说明和示例 , 详 见
Hibernate 官方 开发手册 ( Chapter 11)。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
数据关联
一对一关联
配置:
Hibernate 中的一对一关 联由 “ one-to-one ”节点定义。
在我们 的权限管理系统示例 中,每个用户 都从属于一个用户 组。如用户 “Erica”
从 属于 “ System Admin ” 组, 从 用户 的 角度 出 发,这就 是一 个典型 的 (单 向 ) 一对
一关系 。
每 个 用 户 对应 一 个 组 , 这 在 我们 的系统 中反 映为 TUser 到 TGroup 的
one-to-one 关系。其 中 TUser 是主控 方,TGroup 是被动 方。
one-to-one 关系定 义比较 简单,只需在主控 方加以定义。 这里, 我们的目 标是
由 TUser 对象获取其对应 的 TGroup 对象 。因此 TUser 对象 是主控 方,为了 实 现一
对一关系 ,我们 在 TUser 对象 的 映射 文件 TUser.hbm.xml 中加入 one-to-one 节
点 ,对 TGroup 对象 进 行 一对一 关 联:
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
>
……
<one-to-one
name="group"
class="org.hibernate.sample.TGroup"
cascade="none"
outer-join="auto"
constrained="false"
/>
……
</class >
</hibernate-mapping >
如果 采 用 XDoclet,则 对 应 的 Tag 如下:
/**
* @hibernate.class
* table="t_user"
* dynamic- update="true"
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
* dynamic- insert="true"
*
*/
public class TUser implements Serializable {
……
private TGroup group;
/**
* @hibernate.one- to- one
* name="group"
* cascade="none"
* class="org.hibernate.sample.TGroup"
* outer- join="auto"
* @return
*/
public TGroup getGroup() {
return group;
}
……
}
one-to-one 节点有以 下 属性:
属性 描述 类型 必须
name
class
cascade
映射 属性
目 标映射类。
注意 要设为 包含 Package name 的全路
径 名称 。
操作 级联(cascade)关系。
可 选值 :
all : 所有情况 下均进行级 联操 作。
none:所有情况 下均不进 行级联 操作。
save-update:在执行 save-update 时
进 行级 联 操作。
delete:在 执 行 delete 时进行级 联操 作。
级 联(cascade)在 Hibernate 映射关
系中是个 非常重要的概念。它指的是当 主
控 方 执 行操作时,关 联对 象(被动方) 是
否同步执行 同一 操作。如对主控对象调 用
save-update 或 delete 方法 时,是 否同
时对关 联对象(被动 方) 进行
Text N
Text N
Text N
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
save-update 或 delete。
这里 ,当 用 户 ( TUser)被 更新 或者删除
时,其 所关 联的组(TGroup)不应被修
改或者删除,因 此, 这里 的级联关系设置
为 none。
constrained
outer-join
property-ref
约束
表明 主控 表的 主键上是 否 存在一 个外键
(foreign key )对其 进行约束。这个选
项 关系到 save、delete 等方法 的级 联 操
作顺序 。
是否使 用外 联接。
true:总 是使 用 outer-join
false:不使用 outer-join
auto(默认 ) :如果关 联对象没 有采 用
Proxy 机制,则使 用 outer-join.
关联类 中用于与主控 类相关 联的属性名
称。
默认 为关联 类 的 主键属性名 。
这里我们 通过主键达成一对一的关联,所
以 采 用 默认 值即 可。如果一对一的关联 并
非建立在主键之间 ,则可 通过此参数指定
关联属性 。
Bool N
Text N
Text N
access
一对多关联
一对 多关系在 系统 实现中 也很常 见。 典型的例子就 是 父亲与 孩 子 的 关系 。 而在我
们现在的这个示例 中,每个 用户(TUser)都 关联 到 多个 地址(TAddress) ,如一个
用 户可 能拥 有 办公 室 地址 、 家庭地址等 多个 地址属性 。 这样 ,在 系统 中, 就反 应为 一
个“一对 多”关联。
一对 多关系分为 单 向一对多 关系 和双向 一对 多关系 。
单向一对多 关系只需在 “一 ”方进行配置 ,双向 一对多 关系需要 在 关联双 方均 加
以配置。
Ø 单向一对 多关系
属性 值的读取 方式。
可选项 :
field
property( 默认)
ClassName
Text N
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
配置:
对 于主控 方 (TUser):
TUser.hbm.xml:
<hibernate-mapping >
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
>
……
<set
name="addresses"
table="t_address"
lazy="false"
inverse="false"
cascade="all"
sort="unsorted"
order-by="zipcode asc"
>
<key
column="user_id"
>
</key >
<one-to-many
class="org.hibernate.sample.TAddress"
/>
</set >
……
</class >
</hibernate-mapping >
对 应 的 XDoclet Tag 如下:
/**
* @hibernate.collection- one- to- many
* class="org.hibernate.sample.TAddress"
*
* @hibernate.collection- key column="user_id"
*
* @hibernate.set
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
* name="addresses"
* table="t_address"
* inverse="false"
* cascade="all"
* lazy="false"
* sort=” unsorted”
* order- by="zipcode asc"
*
*/
public Set getAddresses() {
return addresses;
}
被动 方(Taddress)的记 录 由 Hibernate 负 责读取 ,之后 存 放在主 控 方 指定的
Collection 类型 属性中。
对 于 one-to-many 关 联 关系 ,我们 可 以采 用 java.util.Set ( 或者
net.sf.hibernate.collection.Bag)类 型的 Collection,表现在 XML 映射文件
中 也就 是 <set>… </set> (或<bag>… </bag> )节点。关于 Hibernate 的 Collection
实现,请参 见 Hibernate Reference.
one-to-many 节点有以下 属性 :
属性 描述 类型 必须
name
table
lazy
inverse
cascade
映射 属性
目 标关联数据库 表。
是否采用延迟 加载 。
关 于延迟加 载 ,请参 见 后面 相关 章节。
用于标识双向 关 联中的被动方一端 。
inverse=false 的一方(主控 方 )负责
维护关联关系。
默认值: false
操作 级联(cascade )关系。
可 选值 :
all : 所有情况 下均进行级 联操 作。
none:所有情况 下均不进 行级联 操作。
save-update:在执行 save-update 时
进 行级 联 操作。
Text Y
Text Y
Text N
Bool N
Text N
delete:在 执 行 delete 时进行级 联操 作。
sort
排 序类型。
Text N
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
可 选值 :
unsorted :不排序( 默认)
natural :自然顺序(避免与 order-by
搭 配使用)
comparatorClass :指以某 个实现了
java.util.Comparator 接口的类 作为排
序 算法。
order-by
where
outer-join
指定 排 序 字段及其排 序 方式。
(JDK1.4 以上 版本有效) 。
对 应 SQL 中的 order by 子句。
避 免与 sort 的 “ natural ”模式同 时 使
用。
数据 甄选条件,如果只 需 要 处理库表中 某
些特定数据 的时候 ,可 通过 此选项设定结
果集限定 条件。
是否使 用外 联接。
true:总 是使 用 outer-join
false:不使用 outer-join
auto(默认 ) :如果关 联对象没 有采 用
Proxy 机制,则使 用 outer-join.
batch-size 采用延迟加载 特性 时(Lazy Loading)
一次读入的数据数量。
此 处未采用 延迟 加载 机制 ,因此此属性忽
略。
Text N
Text N
Text N
Int N
access
通 过单向一对多关系 进 行关 联 相对 简单 ,但 是 存 在一 个问题。由 于是 单 向 关 联,
为了保持关联 关系,我们只 能通 过主控方对被动 方进 行级联更新。且 如果被关 联方的
关 联 字 段为 “ NOT NULL”,当 Hibernate 创建 或者更新关联 关系 时,还 可 能出 现 约
束违例。
例 如我们想为 一 个已 有的用 户“Erica”添 加一个地址对象 :
Transaction tx = session.beginTransaction();
TAddress addr = new TAddress();
属性 值的读取 方式。
可选项 :
field
property( 默认)
ClassName
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Text N
Hibernate Developer’ s Guide Version 1.0
addr.setTel("1123" );
addr.setZipcode("233123" );
addr.setAddress("Hongkong" );
user.getAddresses().add(addr);
session.save(user);//通过主控对 象级联 更新
tx.commit();
为 了完成这个操 作, Hibernate会分两 步 (两条 SQL ) 来 完成新增 t_address
记 录的操 作 :
1. save(user)时:
insert into t_address (user_id, address, zipcode, tel)
values (null, "Hongkong" , "233123" , "1123" )
2. tx.commit()时
update t_address set user_id=” 1” , address="Hongkong" ,
zipcode="233123" , tel="1123" where id=2
第一条SQL用于插入新的地址记 录。
第二 条SQL用于更新 t_address,将 user_id设置 为其关 联的user对象 的id值。
问题就出 在这里 ,数据库 中,我们 的t_address.user_id字段为 “ NOT NULL”
型,当Hibernate执 行 第一条语句创 建t_address记 录时,试图将 user_id 字段的
值设为 null,于 是引发了 一个 约束违例异常 :
net.sf.hibernate.PropertyValueException: not-null property
references a null or transient value:
org.hibernate.sample.TAddress.userId
因 为关联方向 是 单向 , 关 联 关系 由 TUser对 象 维 持,而被关联的 addr对 象 本身 并
不知道自己与 哪个 TUser对象相关联,也就是说,addr对象 本身并不 知道 user_id应
该 设为什么数值。
因此,在保存addr 时, 只能先在关 联字 段插入一个空 值。之后,再由TUser对象
将自 身 的 id值 赋予关 联 字段addr.user_id,这个赋 值 操作 导 致addr对 象 属性发生变
动,在事务提交 时,hibernate会 发现这一改变,并通过update sql将 变动 后 的数
据 保存 到数据库 。
第一个步骤 中,企图向数据库的非空 字段插入空 值,因此 导致了约束违例。
既然 TUser对象是 主控 方, 为 什么 就不能 自动先设置 好下 面 的 TAddress对象的
关俩 字段 值再一 次做Insert操作呢?莫名其 妙? Ha, don’ t ask me ,go to ask
Hibernate TeamJ。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
我们 可以 在 设计 的时 候通过一些手段 进 行调整 ,以 避免这 样 的约束违例,如将 关
联字段设为允许NULL值、 直接采用数值型字段 作为关 联(有的时候这 样 的调整 并不可
行 ,很 多 情况下我们 必须针对现有数据库结构 进 行开 发 ) ,或者 手动 为 关联字 段属性
赋 一个 任意 非空 值( 即使 在 这里通过 手工 设置了 正 确的user_id也没有意义 ,
hibernate还是 会自动再调用一条 Update语句进行 更新)。
甚 至我们可以 将 被动 方的关联 字段 从 其 映射 文件中剔除(如将user_id字段的 映
射从TAddress.hbm.xml中剔除)。这样Hibernate在 生成第一条 insert 语句的时
候 就不会包含这 个 字 段 (数据库会使用 字 段 默认 值 填充 ) ,如:之后update语句 会根
据主控方的one-to-many 映射配置中的关联字段去 更新 被动方 关联 字段的内容 。在 我
们 这里的例子 中,如果将 user_id字段 从 TAddress.hbm.xml 文件中 剔除 ,
Hibernate在保存数据时会 生成下面几 条 SQL:
1. insert into t_address (address, zipcode, tel) values
('Hongkong', '233123', '1123')
2. update t_address set user_id=1 where id=7
生成 第一条insert语句 时, 没 有 包含 user_id字段, 数据库 会使用该字段 的 默
认值(如果有的话 )进行填充。因此不 会 引发约束违例。之后 ,根据 第 一条语句返回
的记录id,再通过 update语句对 user_id字段进行 更新。
但 是,纵使采 用 这 些权 益之计,由于Hibernate实现机制 中, 采 用 了两 条 SQL进
行一次数据插 入操 作,相 对单 条insert ,几乎 是两倍的性能开销,效率较 低,因此,
对 于性 能 敏 感 的 系统而言, 这 样的 解 决方案所带来 的 开销 可 能难以承受。
针对上面 的情况 ,我们想到,如果addr对象 知道 如何获取user_id字段的 内容,
那么执行insert语句的时候直接 将数据植 入即 可。这 样不但绕 开了约束违 例的可能 ,
而 且还节省了 一条 Update语句的开 销 ,大 幅 度提 高了性能 。
双向一对多关系 的出 现则解决了 这个问题。它除了避 免约束违例和提高性能的好
处 之外, 还 带来另外一个 优点,由 于 建立了双 向 关 联,我们 可 以 在 关 联 双方中任意一
方,访 问关 联的另 一方(如可以 通过TAddress对 象直接访问其 关联的TUser对 象),
这 提供了 更 丰富灵活的控制手 段 。
Ø 双向一对 多关系
双 向一对多关系 , 实际上 是 “单 向 一对多关系” 与 “ 多 对一 关系” 的 组 合。 也 就
是说我们必须 在主控 方配置单 向 一对多关系 的基础上 ,在被控 方配置多 对一关系与其
对应 。
配置:
上 面我们已 经大 致完 成 了 单 向方一对多 关系 的配置 , 我们只 需在 此 基础上 稍做修
改,并 对(t_address)的 相关属性进行 配置即 可:
TUser.hbm.xml:
<hibernate-mapping >
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
>
……
<set
name="addresses"
table="t_address"
lazy="false"
inverse="true" ①
cascade="all"
sort="unsorted"
order-by="zipcode asc"
>
<key
column="user_id"
>
</key >
<one-to-many
class="org.hibernate.sample.TAddress"
/>
</set >
</class >
</hibernate-mapping >
① 这里与前 面不 同,inverse被设为“true ” ,这意味着 TUser 不 再 作 为主控 方,
而是将关 联关系 的维护工作交给关联对 象 org.hibernate.sample.TAddress 来
完 成。这样 TAddress 对象在 持久化 过程 中, 就可 以 主动获取其关联的 TUser 对 象 的 id ,
并将其 作为 自己 的 user_id ,之 后执行一 次 insert 操作 即可 完 成全部 工作。
在 one-to-many 关系 中,将 many 一方设为主动方( inverse=false) 将有助性能
的改善 。( 现实 中也 一样 ,如果要让胡锦涛记 住 全国人民 的名字 ,估计 花个几 十年也
不 可能 ,但要 让 全 国人民知道胡锦涛,可就不需要 那么多 时 间了 。 J)
对应 的 xdoclet tag 如下:
public class TUser implements Serializable {
……
private Set addresses = new HashSet();
……
/**
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
* @hibernate.collection- one- to- many
* class="org.hibernate.sample.TAddress"
*
* @hibernate.collection- key column="user_id"
*
* @hibernate.set
* name="addresses"
* table="t_address"
* inverse="true"
* lazy="false"
* cascade=” all”
* sort="unsorted"
* order- by="zipcode asc"
*/
public Set getAddresses() {
return addresses;
}
……
}
TAddress.hbm.xml:
<hibernate-mapping >
<class
name="org.hibernate.sample.TAddress"
table="t_address"
dynamic-update="false"
dynamic-insert="false"
>
……
<many-to-one
name="user" ①
class="org.hibernate.sample.TUser"
cascade="none"
outer-join="auto"
update="true"
insert="true"
access="property"
column="user_id"
not-null="true"
/>
</class >
</hibernate-mapping >
① 在 TAddress 对象 中新增 一个 TUser field “user ” , 并 为其添加 对应 的
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
getter/setter 方法 。 同时 删除原有 的 user_id 属性及其映射配置, 否则运行 期会报
字段 重复映射 错误: “Repeated column in mapping ”。
对应 Xdoclet tag:
public class TAddress implements Serializable {
……
private TUser user;
……
/**
* @hibernate.many- to- one
* name="user"
* column="user_id"
* not- null="true"
*
*/
public TUser getUser() {
return this .user;
}
……
}
再 看上面那段 代码 片断:
Criteria criteria = session.createCriteria(TUser.class );
criteria.add(Expression.eq("name" ,"Erica" ));
List userList = criteria.list();
TUser user =(TUser)userList.get(0);
Transaction tx = session.beginTransaction();
TAddress addr = new TAddress();
addr.setTel("1123" );
addr.setZipcode("233123" );
addr.setAddress("Hongkong" );
user.getAddresses().add(addr);
session.save(user);//通过主控对 象级联 更新
tx.commit();
尝 试运 行 这段 代码 , 结 果凄凉的很 , 还 是 约束违例。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
为 什么会这 样 ,我们已经配置了 TAddress 的 many-to-one 关系, 这 么 看 来似
乎没 什么效果……
不过 ,别忘 了 上 面 提到的 inverse 属性 , 这里我们把 TUser 的 inverse 设为
“true”,即指定由对方维护关 联 关系,在 这里也就是由 TAddress 维护 关联 关系。
TUser 既然不再维护关联 关系 ,那么 TAddress 的 user_id 属性它 也 自然不 会 关心,
必须由 TAddress 自己去 维护 user_id:
……
TAddress addr = new TAddress();
addr.setTel("1123" );
addr.setZipcode("233123" );
addr.setAddress("Hongkong" );
addr.setUser(user);//设置关联的 TUser对象
user.getAddresses().add(addr);
session.save(user);//级联更新
……
观察 Hibernate 执 行过程中 调 用的 SQL 语句:
insert into t_address (user_id, address, zipcode, tel) values
(1, 'Hongkong', '233123', '1123')
正 如我们所期望 的, 保 存工作通过 单 条 Insert 语句的执 行来完 成 。
many-to-one 节点有以 下 属性:
属性 描述 类型 必须
name
column
class
cascade
update
映射属性
关联 字段。
类名
默认为映射 属性所 属类型
操作 级联(cascade )关系。
可 选值 :
all : 所有情况 下均进行级 联操 作。
none:所有情况 下均不进 行级联 操作。
save-update:在执行 save-update 时
进 行级 联 操作。
delete:在 执 行 delete 时进行级 联操 作。
是 否 对 关 联 字段 进 行 Update 操作
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Text Y
Text N
Text N
Text N
Bool N
Hibernate Developer’ s Guide Version 1.0
默认 :true
insert
outer-join
property-ref
access
是 否对 关联字段 进 行 Insert 操作
默认:true
是 否使 用 外 联 接。
true:总 是使 用 outer-join
false:不使用 outer-join
auto(默认 ) :如果关 联对象没 有采 用
Proxy 机制,则使 用 outer-join.
用 于与主控 类 相关 联的 属性 的 名 称。
默认为关联 类的 主键属性名。
这里 我们通 过主键进行关 联,所 以采 用 默
认 值 即可。如果关 联并非建立在主键之
间 ,则 可 通 过 此参数 指定 关联 属性 。
属性值的读取 方式。
可 选项 :
field
property( 默认)
Bool N
Text N
Text N
Text N
ClassName
级联与关 联关系的差别?
多对 多关联
Hibernate 关联关系 中相 对比较特殊 的 就 是 多对多 关联,多对多 关联 与 一对一关
联和一对多关 联不同 ,多对多关联需要 另外一张映射表用于保存 多 对多 映射信息。
由于多对多关联的性 能不佳(由 于引入了 中间表,一 次读取操作需要反 复数 次查
询 ),因此 在 设计 中应该避 免 大 量使 用。同 时,在对 多 对关系 中, 应根 据 情况 , 采取
延迟加载(Lazy Loading 参见后续 章节)机制来避免无谓 的性能开销。
在一个 权限管理系统 中,一个常见的多 对多 的映射关系就是 Group 与 Role,以
及 Role 与 Privilege 之间 的映射。
Ø Group 代表 “组 ”(如 “ 业务主管”);
Ø Role 代表 “角色 ”(如 “出纳 ”、 “财务”);
Ø Privilege 代表某个特定资源的访 问权 限(如 “修改财务 报表” ,“ 查询
财 务报表”)。
这里我们以 Group 和 Role 之间的 映射为例:
Ø 一个 Group 中 包含了多个 Role ,如 某个“ 业务 主管”拥有 “出 纳 ” 和 “ 财
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
务”的双 重角色。
Ø 而一 个 Role 也可以 属 于不同的 Group。
配置:
在 我们的实例 中, TRole 和 TPrivilege 对 应数据库 中的 t_role 、
t_privilege 表。
TGroup.hbm.xml 中关于多对 多关 联的配置片断:
<hibernate-mapping>
<class
name="org.hibernate.sample.TGroup"
table="t_group"
dynamic-update="false"
dynamic-insert="false"
>
……
<set
name="roles"
table="t_group_role" ①
lazy="false"
inverse="false"
cascade="save-update" ②
>
<key
column="group_id" ③
>
</key >
<many-to-many
class="org.hibernate.sample.TRole"
column="role_id" ④
/>
</set >
</class >
</hibernate-mapping >
① 这里为 t_group 和 t_role 之间的映射 表。
② 一般情况 下,cascade 应该 设置为“save-update ” ,对 于多对 多逻辑
而言,很少出现删除 一方需要 级 联删除所有 关联数据的情况 , 如删除一
个 Group,一 般不会删除 其中包含的 Role(这些 Role 可 能还 被其 他 的
Group 所引用)。反之删除 Role 一 般也不会删除其 所 关联的 所有 Group。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
③ 映射表中对于 t_group 表记录的标识字 段。
④ 映射 表中对 于 t_role 表记录的标 识字段。
对应 的 xdoclet tag 如下:
public class TGroup implements Serializable {
……
private Set roles = new HashSet();
/**
* @hibernate.set
* name="roles"
* table="t_group_role"
* lazy="false"
* inverse="false"
* cascade="save- update"
* sort=” unsorted”
*
* @hibernate.collection- key
* column="group_id"
*
* @hibernate.collection- many- to- many
* class="org.hibernate.sample.TRole"
* column="role_id"
*
*/
public Set getRoles() {
return roles;
}
……
}
TRole.hbm.xml 中关于多对 多关 联的配置片断:
<hibernate-mapping>
<class
name="org.hibernate.sample.TRole"
table="t_role"
dynamic-update="false"
dynamic-insert="false"
>
……
<set
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
name="groups"
table="t_group_role"
lazy="false"
inverse="true"
cascade="save-update"
sort="unsorted"
>
<key
column="role_id"
>
</key >
<many-to-many
class="org.hibernate.sample.TGroup"
column="group_id"
outer-join="auto"
/>
</set >
</class >
</hibernate-mapping >
对 应 的 xdoclet 如下:
public class TRole implements Serializable {
private Set groups = new HashSet();
……
/**
*
* @hibernate.set
* name="groups"
* table="t_group_role"
* cascade="save- update"
* inverse="true"
* lazy="false"
*
* @hibernate.collection- key
* column="role_id"
*
* @hibernate.collection- many- to- many
* class="org.hibernate.sample.TGroup"
* column="group_id"
*
*
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
*/
public Set getGroups() {
return groups;
}
}
many-to-many 节点中 各个 属性描述:
属性 描述 类型 必须
column
class
outer-join
中间映射表中,关联目 标表的关联字段。
类名
关 联目 标 类。
是否使 用外 联接。
true:总 是使 用 outer-join
false:不使用 outer-join
auto(默认 ) :如果关 联对象没 有采 用
Proxy 机制,则使 用 outer-join.
Text Y
Text Y
Text N
使用:
多对 多关系中,由于 关联 关系是两张 表 相互引用, 因此在 保 存 关联状态 时必须 对
双方同 时保存 。
public void testPersist(){
TRole role1 = new TRole();
role1.setName("Role1" );
TRole role2 = new TRole();
role2.setName("Role2" );
TRole role3 = new TRole();
role3.setName("Role3" );
TGroup group1 = new TGroup();
group1.setName("group1" );
TGroup group2 = new TGroup();
group2.setName("group2" );
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
TGroup group3 = new TGroup();
group3.setName("group3" );
group1.getRoles().add(role1);
group1.getRoles().add(role2);
group2.getRoles().add(role2);
group2.getRoles().add(role3);
group3.getRoles().add(role1);
group3.getRoles().add(role3);
role1.getGroups().add(group1);
role1.getGroups().add(group3);
role2.getGroups().add(group1);
role2.getGroups().add(group2);
role3.getGroups().add(group2);
role3.getGroups().add(group3);
try {
Transaction tx = session.beginTransaction();
//多对 多关系必须同时对关 联双 方进行 保 存
session.save(role1);
session.save(role2);
session.save(role3);
session.save(group1);
session.save(group2);
session.save(group3);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
Assert.fail(e.getMessage());
}
}
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
上面的 代码创 建 3 个 TGroup 对象和 3 个 TRole 对象,并形 成了多 对多 关系。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
数据 访问
PO 和 VO
PO 即 P ersistence O bject
VO 即 V alue O bject
PO 和 VO 是 Hibernate 中两个比较关键 的概念。
首 先,何谓 VO,很 简单 ,VO就 是一 个 简单 的值 对象 。
如:
TUser user = new TUser();
user.setName("Emma" );
这里 的 user 就 是一 个 VO。VO 只是 简单 携 带 了对象 的一些 属性信息 。
何谓 PO? 即纳入 Hibernate 管理 框架中的 VO。看 下面两 个例子:
TUser user = new TUser();
TUser anotherUser = new TUser();
user.setName("Emma" );
anotherUser.setName("Kevin" );
//此时 user和 anotherUser都是 VO
Transaction tx = session.beginTransaction();
session.save(user);
//此时的 user已经经过 Hibernate的处理 ,成为 一个PO
//而 anotherUser仍然是 个 VO
tx.commit();
//事务 提交之后 ,库 表中已经插入 一条用户” Emma”的记录
//对于 anotherUser则无任何操 作
Transaction tx = session.beginTransaction();
user.setName("Emma_1" ); //PO
anotherUser.setName("Kevin_1" );//VO
tx.commit();
//事务 提交之后 , PO的状态被固化到数据库中
//也就是说数据库 中“ Emma”的用户记录已经 被更新为 “Emma_1”
//此时 anotherUser仍然是 个普通 Java对象 ,它 的属性更改不 会
//对数据库产 生 任何影响
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
另外 ,通 过 Hibernate返回 的对象也 是 PO:
//由 Hibernate返回的 PO
TUser user = (TUser)session.load(TUser.class ,new Integer(1));
VO经过 Hibernate进行处 理 ,就变成了 PO。
上 面的示例 代码 session.save(user)中,我们把一个 VO “user” 传递给
Hibernate的 Session.save方法 进行保存 。在 save方法 中, Hibernate对其进
行 如下处理 :
1. 在当 前session所对应 的实 体容器(Entity Map)中查询 是否存 在 user对象
的引用。
2. 如果引用存 在,则直接返回user对象 id, save过程结束.
Hibernate中,针对 每个Session有一 个实体 容器(实际上 是一个Map对 象),
如果 此容器 中 已经保 存了目标对 象 的引用,那么hibernate 会认 为此 对象已经
与Session 相关联。
对于save操作而言 , 如果对象已经与Session 相关 联(即已经被加入Session
的实 体容器 中),则无需进行具 体的操作。因为之后的Session.flush 过程中,
Hibernate 会对 此 实体 容 器 中的对象进 行遍历 , 查找出发 生 变 化 的实 体, 生成
并执行 相应的update语句。
3. 如果引用不存 在,则根 据映射关系 ,执行insert操 作。
a) 在我们这里的示例 中, 采用 了 native的 id生成机制,因此hibernate会
从数据库 取得insert操作 生成 的id并赋予 user 对象 的 id属性。
b) 将 user对象 的引用纳入 Hibernate的实 体容器 。
c) save过程结束,返 回对象 id.
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
而 Session.load方法 中, 再返回对象之 前, Hibernate就已 经 将此 对 象纳 入 其实
体容器 中。
VO 和PO 的主要区 别 在于 :
Ø VO是独立 的 Java Object。
Ø PO是由 Hibernate纳入其实体 容器(Entity Map)的对象 ,它代 表了与数
据库 中某 条 记 录 对 应 的Hibernate实体,PO的变化 在事 务 提 交时将反 应到实
际数据库中。
如果一 个PO与 Session 对应 的 实 体 容器 中分 离( 如 Session 关闭 后 的 PO ),那么
此时,它又会变成 一个VO。
由 PO 、 VO的概念,又引 申 出一些 系统 层次 设计 方 面 的问题。如在传 统 的MVC架构 中,
位于Model层的 PO,是否 允 许 被 传递到 其 他 层面。由于PO的 更新最终 将被 映射到 实
际数据库中,如果PO在其他 层面(如View层)发 生了变动, 那么可能会 对Model
层造成意想 不到的破坏。
因 此,一 般 而言, 应 该 避 免直接PO传递到系统中的其他 层面 ,一 种解 决办法 是, 通
过一 个VO,通过 属性复制使其具备 与PO相 同属性值, 并以其为传输 媒 质(实际上,
这 个VO被 用作Data Transfer Object,即 所 谓 的 DTO ) ,将 此VO传递 给其 他 层
面以实现必须 的数据 传送。
属性 复制可以通过 Apache Jakarta Commons Beanutils
(http://jakarta.apache.org/commons/beanutils/ )组件提供 的 属性批
量复制 功能, 避 免繁 复 的get/set操 作。
下面 的例子 中,我们把user对象 的所有属性复制到anotherUser对象 中:
TUser user = new TUser();
TUser anotherUser = new TUser();
user.setName("Emma" );
user.setUserType(1);
try {
BeanUtils.copyProperties(anotherUser,user);
System.out.println("UserName => "
+anotherUser.getName()
);
System.out.println("UserType => "
+ anotherUser.getUserType()
);
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
关于 unsaved-value
在非显示数据保存时,Hibernate将根 据这个 值来判断对象 是否 需要保存。
所谓显 式保存 ,是指 代码 中明确调 用 session 的 save、update、saveOrupdate方
法 对对象 进 行持久化。 如:
session.save(user);
而在某 些情况 下,如映射关系 中,Hibernate 根据 级联(Cascade )关系对联 接类进
行保存。 此时代码 中没 有针 对 级联对象 的显示 保存语句,需要 Hibernate 根据对 象当前状
态判断 是否 需 要保存到数据库 。 此 时, Hibernate 即将 根 据 unsaved-value 进行判定。
首先 Hibernate 会取出目 标对象的 id 。
之 后,将此 值 与 unsaved-value 进行比 对 ,如果 相等 , 则认为目标对 象 尚未保 存 ,否
则,认为 对 象已经 保存,无需再 进行保存 操作。
如:user 对象 是之 前由 hibernate 从数据库 中获取 ,同 时,此 user 对象 的若干 个关
联对象 address 也被加载 , 此时我们向 user 对象新 增一个 address 对象 ,此 时调 用
session.save(user) ,hibernate 会根据 unsaved-value 判断 user 对象的 数个 address
关联对象中,哪 些需要执行 save 操 作,而哪 些不 需要 。
对 于我们新加 入 的 address 对象而 言 ,由 于其 id( Integer 型)尚未赋 值 , 因此为
null,与我们设 定的 unsaved-value( null)相同,因 此 hibernate 将其视 为 一个未保存
对 象,将 为其生成 insert 语 句 并 执 行 。
这里可能会产 生一个疑问,如果“原有”关联对象发生变动(如 user 的 某个“原有 ”
的 address 对象 的 属性 发 生了变化,所 谓“ 原有 ”即此 address 对象已经与 user 相关 联,
而不是我们在 此 过程中 为之新增的 ),此时 id 值是 从数据库 中读出,并没有发 生改变 ,自然
与 unsaved-value( null)也不一样 ,那么 Hibernate 是不是 就不保存 了?
上 面关 于 PO、 VO 的讨论 中 曾经涉及到数据保存的 问题, 实际上 , 这里 的 “保存 ” ,
实际上是 “insert”的 概念,只 是针 对新关 联对象 的加入 ,而非数据库 中原有关 联对象 的
“update”。所谓新关联对 象,一般情况 下可 以理解为未 与 Session 发生关联的 VO。而
“原有 ”关联对象,则是 PO 。如上面 关于 PO、 VO 的讨论 中所述 :
对 于save操作而言 , 如果对 象已经与Session 相关 联 (即已经被加入Session的实 体
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
容 器中) , 则无需进行具 体的 操作。 因为之后的Session.flush 过程 中,Hibernate
会对 此实体 容 器中的对象 进行遍历 ,查找出发 生变化的实 体,生成并执行 相应的update
语句。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
Inverse 和 Cascade
Inverse ,直译为“反转 ” 。在 Hibernate 语义 中,Inverse 指定了关 联 关系 中的
方向 。
关 联 关系 中,inverse=” false ”的为主动 方,由 主动 方 负责维护 关 联 关系 。具体可
参见一对多关系 中的描述。
而 Cascade,译为“级联 ”,表明对象 的级 联关系 ,如 TUser 的 Cascade 设为 all,
就表明如果发生 对 user 对象 的操 作,需 要 对 user 所关 联的对象也 进行同样 的操 作。如对
user 对象执行 save 操作,则必须 对 user 对象相关联的 address 也执行 save 操作。
初 学者常常 混淆 inverse 和 cascade ,实际上,这是两个互 不 相关 的概念 。 Inverse
指 的是关 联 关系 的 控制方向 , 而 cascade 指 的是 层级之间的连锁 操 作。
延迟加载(Lazy Loading )
为了避免一些情况 下,关 联关系 所带来 的无谓 的性能开销 。 Hibernate引入了延迟加载 的
概念 。
如,示例 中 user对象 在加载 的时候,会同 时读取其 所关联的多个地址(address)对 象,
对 于 需 要 对 address进 行操 作的 应 用 逻辑而 言 , 关 联 数据 的自 动 加 载机制的确 非常 有 效 。
但是,如果我们只是想 要获得user的 性别(sex)属性,而不关心user的 地址(address)
信息,那么自 动加载address的 特性就 显得多余,并 且造成了极大的 性能浪费。为了获 得user
的 性别属性,我们可能 还要同 时从数据库 中 读取数 条无用的 地址 数据,这导致了大量无谓 的 系统
开销。
延迟 加载特性 的 出 现, 正是为了解 决 这个问题。
所谓延迟 加载 ,就 是在需要数据 的时候 ,才真正执行数据加载操 作。
对于我们这里 的user对象 的加载过程 ,也就意味着 ,加 载 user对象 时 只针 对其本身 的 属性,
而当 我们需 要 获取user对象 所 关 联的 address 信息时(如执行user.getAddresses时),才
真正从数据库中加载address 数 据并 返回。
我们 将前面 一对 多关系中的lazy属性 修改 为true,即指定了关 联对 象采 用 延迟加载 :
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
>
……
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
<set
name="addresses"
table="t_address"
lazy="true" ★
inverse="false"
cascade="all"
sort="unsorted"
order-by="zipcode asc"
>
<key
column="user_id"
>
</key >
<one-to-many
class="org.hibernate.sample.TAddress"
/>
</set >
……
</class >
</hibernate-mapping >
尝试执 行以下代码 :
Criteria criteria = session.createCriteria(TUser.class );
criteria.add(Expression.eq("name" ,"Erica" ));
List userList = criteria.list();
TUser user =(TUser)userList.get(0);
System.out.println("User name => " +user.getName());
Set hset = user.getAddresses();
session.close();//关闭 Session
TAddress addr = (TAddress)hset.toArray()[0];
System.out.println(addr.getAddress());
运 行时抛出异 常 :
LazyInitializationException - Failed to lazily initialize a
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
collection - no session or session was closed
如果 我们稍做调整 ,将 session.close放在代码 末尾 , 则不会 发 生这样 的问题。
这意味着 ,只 有我们实际加载user 关联的address 时,Hibernate才 试图 通过
session从数据库中加 载实际的数据集,而 由于我们读取address之前 已经关闭 了
session,所以报出 session已关闭的错误。
这里有个 问题,如果我们采 用了延迟加载机制,但 希望在一些情况 下,实 现非延迟 加
载时的功能 ,也就 是说,我们 希望在Session关闭 后,依然允 许操 作user 的addresses
属性 。如,为了向View层提 供 数据,我们必须提供 一 个完 整 的 User对象 , 包含其 所 关 联的
address信息,而这 个 User对象必须在 Session关闭之后仍然可 以使用。
Hibernate.initialize方法 可以通 过强制 加载关联对 象实现 这一功能 :
Hibernate.initialize(user.getAddresses());
session.close();
//通过 Hibernate.initialize方法强 制读取数据
//addresses对象即 可脱离 session进行操 作
Set hset= user.getAddresses();
TAddress addr = (TAddress)hset.toArray()[0];
System.out.println(addr.getAddress());
为 了 实 现透 明 化 的 延迟加载 机制 , hibernate进 行 了 大 量努 力 。 其 中包括 JDK
Collection接口的独立实 现。
如果 我们尝试 用 HashSet强行转化 Hibernate 返回的Set型 对 象 :
Set hset = (HashSet)user.getAddresses();
就 会在运 行 期得 到一 个 java.lang.ClassCastException, 实际 上,此时返回 的是
一个Hibernate的特定 Set实现“net.sf.hibernate.collection.Set ”对象 ,而非
传统 意义上的 JDK Set实 现。
这 也正是 我们 为什么在编写POJO时, 必须用JDK Collection接口 ( 如 Set,Map ) ,
而非特定的 JDK Collection 实现类( 如HashSet 、HashMap)申 明Collection属性的
原因 。
回 到前 面 TUser类的定 义:
public class TUser implements Serializable {
……
private Set addresses = new HashSet();
……
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
}
我们 通过Set接口,申 明 了 一 个 addresses 属性 ,并创 建 了 一个HashSet作为
addresses的初始实例 ,以便我们创建 TUser 实例后,就可 以为其添加 关 联的 address对
象 :
TUser user = new TUser();
TAddress addr = new TAddress();
addr.setAddress("Hongkong" );
user.getAddresses().add(addr);
session.save(user);
此 时,这里的addresses 属性还 是一 个HashSet对象 , 其中包含了一个 address 对 象
的引用。那么 ,当调 用session.save(user)时,Hibernate 是如何处理这 个HashSet
型属性的呢?
通过Eclipse的 Debug窗口,我们可以看到session.save方 法执 行 前后user对象发
生的变化 :
图 一 session.save 方 法之 前的 user 对 象
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
图二 session.save方法之后 的user对象
可以看到, user 对象 在通过Hibernate处 理之 后已经 发生了变 化 。
首 先,由于insert操作, Hibernate 获得 数据库 产 生 的id 值( 在我们的例 子 中,采
用native 方式的主键 生成机制),并填充到user对象 的id 属性。这个变化比较容 易理解。
另 一方面 ,Hibernate使用了 自 己 的 Collection 实现
“ net.sf.hibernate.collection.Set ” 对user 中的 HashSet型 addresses 属性进
行了替换,并用 数据对其进行填充,保证新的addresses与 原有的addresses 包含同样 的
实体元素 。
由 于拥 有自 身 的 Collection实 现, Hibernate就可 以 在 Collection 层从 容 的 实 现
延迟 加载特性 。 只 有程序真正读取这个Collection时,才激 发 底层实际 的数 据库 操 作。
事务 管理
Hibernate 是 JDBC 的轻量级封装 ,本身并不具备 事务管理能力 。在事务管理层,
Hibernate 将其委托 给底层的 JDBC 或者 JTA,以实 现事务管理 和调度功 能。
Hibernate 的默认事务处理 机制基于 JDBC Transaction。我们也可以通 过 配置文
件 设定采 用 JTA 作为 事 务管 理实 现:
<hibernate-configuration>
<session-factory>
……
<property name="hibernate.transaction.factory_class">
net.sf.hibernate.transaction.JTATransactionFactory
<!--net.sf.hibernate.transaction.JDBCTransactionFactory-->
</property >
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
……
</session-factory >
</hibernate-configuration >
基于 JDBC 的事 务管理
将事务管理委托 给 JDBC 进行处 理 无疑 是最简单的实 现 方式,Hibernate 对于 JDBC
事务 的封装也 极为简单。
我们 来看下 面 这段 代码 :
session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
……
tx.commit();
从 JDBC 层面而言, 上 面 的代码 实际 上对 应 着:
Connection dbconn = getConnection();
dbconn.setAutoCommit(false );
……
dbconn.commit();
就 是这么 简单 ,Hibernate 并没有做 更 多的事情(实际上也没法 做 更 多 的事情 ) ,只
是将这样 的 JDBC 代码进行了 封装而已 。
这里要注意的是,在 sessionFactory.openSession() 中,hibernate 会初 始化
数据库连 接,与此同 时,将其 AutoCommit 设为 关闭状态 (false)。 而其 后,在
Session.beginTransaction 方 法 中, Hibernate 会 再次确认 Connection 的
AutoCommit 属性被设为关闭 状态(为 了防 止用户代码 对 session 的
Connection.AutoCommit 属性进行 修改) 。
这 也就是说,我们一 开始从 SessionFactory 获得的 session ,其 自 动 提 交 属性 就
已经被关闭 (AutoCommit=false) ,下 面的代码 将不会 对数据库产生任何效 果:
session = sessionFactory.openSession();
session.save(user);
session.close();
这 实际 上相 当于 JDBC Connection 的 AutoCommit 属性被 设为 false,执行了 若
干 JDBC 操作之 后,没 有 调 用 commit 操作即 将 Connection 关闭。
如果要使代码真正 作用到数据库,我们 必须显 式的调 用 Transaction 指令:
session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
session.save(user);
tx.commit();
session.close();
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
基于 JTA 的事 务管理
JTA 提供了跨 Session 的事务管理能力。这 一 点 是 与 JDBC Transaction 最大 的
差异。
JDBC 事务 由 Connnection 管理,也就是说,事 务 管 理实际上 是在 JDBC Connection
中实 现。事 务 周 期 限 于 Connection 的生命周期之类 。同样,对 于 基 于 JDBC Transaction
的 Hibernate 事务管理 机制而言,事 务管理在 Session 所依托的 JDBC Connection
中实 现,事务周 期限于 Session 的生命周 期。
JTA 事务管理则 由 JTA 容器 实 现, JTA 容器对 当 前加入 事 务 的 众多 Connection 进
行调度, 实现 其事务性要 求。JTA 的事务周 期可横跨多个 JDBC Connection 生 命周 期。
同 样对于基 于 JTA 事务 的 Hibernate 而言, JTA 事务 横跨 可 横跨 多个 Session。
下面这幅 图形象 的说明了这个 问题:
图 中描述 的是 JDBC Connection 与事务之 间 的 关系, 而 Hibernate Session 在
这里与 JDBC Connection 具备 同等的 逻辑含义。
从上图中我们 可 以 看出,JTA 事务 是由 JTA Container 维护,而参 与事务 的
Connection 无需对事务管理 进 行干 涉 。这也就 是说,如果 采用 JTA Transaction,我
们不应该再 调用 Hibernate 的 Transaction 功 能。
上 面 基 于 JDBC Transaction 的正确 代码 , 这里 就会产生 问题:
public class ClassA{
public void saveUser(User user){
session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
session.save(user);
tx.commit();
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
session.close();
}
}
public class ClassB{
public void saveOrder(Order order){
session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
session.save(order);
tx.commit();
session.close();
}
}
public class ClassC{
public void save(){
……
UserTransaction tx = new InitialContext().lookup(“……” );
ClassA.save(user);
ClassB.save(order);
tx.commit();
……
}
}
这里 有两个类 ClassA 和 ClassB ,分 别 提 供 了 两 个 方 法 : saveUser 和 saveOrder ,
用于保存 用户信息 和订单信息 。在 ClassC 中,我们接连调用 了 ClassA.saveUser 方法
和 ClassB.saveOrder 方 法 , 同 时引入了 JTA 中的 UserTransaction 以实现
ClassC.save 方法 中的事 务性 。
问题出 现了 ,ClassA 和 ClassB 中分别都调 用了 Hibernate 的 Transaction 功
能 。在 Hibernate 的 JTA 封装中, Session.beginTransaction 同样 也 执 行 了
InitialContext.lookup方法获取 UserTransaction实例,Transaction.commit
方 法同 样 也调 用 了 UserTransaction.commit 方 法 。 实际上,这就形 成 了两个嵌套式的
JTA Transaction: ClassC 申明了 一个 事务 ,而 在 ClassC 事务周 期内 , ClassA 和
ClassB 也企图申明自己 的事务 ,这 将导 致运行期错误。
因 此,如果决 定采 用 JTA Transaction, 应避 免 再重复调用 Hibernate 的
Transaction 功能,上 面的代码修改如下:
public class ClassA{
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
public void save(TUser user){
session = sessionFactory.openSession();
session.save(user);
session.close();
}
……
}
public class ClassB{
public void save (Order order){
session = sessionFactory.openSession();
session.save(order);
session.close();
}
……
}
public class ClassC{
public void save(){
……
UserTransaction tx = new InitialContext().lookup(“……” );
classA.save(user);
classB.save(order);
tx.commit();
……
}
}
上 面 代码 中的 ClassC.save 方法 , 也 可 以 改 成这 样:
public class ClassC{
public void save(){
……
session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
classA.save(user);
classB.save(order);
tx.commit();
……
}
}
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
实际 上, 这 是利用 Hibernate 来完成启动 和提 交 UserTransaction 的功能 , 但这
样的做 法比原本直接通过 InitialContext 获取 UserTransaction 的做 法消耗了更多
的资源,得不偿失 。
在 EJB 中使 用 JTA Transaction 无疑 最为 简便 ,我们只需 要将 save 方法 配置为
JTA 事务支持即可,无需显 式 申 明任何事 务 ,下面 是一 个 Session Bean 的 save 方法 ,
它 的事务属性 被 申 明 为“Required”, EJB 容器 将自动维护此 方 法执行过程中的事务 :
/**
* @ejb.interface- method
* view- type="remote"
*
* @ejb.transaction type = "Required"
**/
public void save(){
//EJB环境中,通过 部 署配置即可实 现事务申 明,而 无需 显式调 用事务
classA.save(user);
classB.save(log);
}//方法结束 时,如果没 有异常 发生 ,则 事务 由 EJB容器自动 提 交。
锁(locking)
业务逻辑的实 现过程 中,往往 需要保证 数据 访问的排他性 。如在金融系统 的日终结 算
处理中,我们希望针对 某个 cut-off 时间点 的 数据进行处理 ,而不希望在 结算进行过程中
( 可能 是 几秒 种 ,也 可 能 是 几个小 时 ) , 数据 再发 生 变化。 此 时, 我们 就需 要 通 过一些机
制来保证这些 数据在 某个操作 过程中 不会 被外界修改,这 样的 机制,在这里 ,也就 是所谓
的 “锁 ” , 即给 我们选定 的目 标数据上锁, 使其 无 法被其他 程 序 修改。
Hibernate 支持两种 锁机制:即通常 所说的“悲观锁(Pessimistic Locking)”
和 “乐观锁 ( Optimistic Locking)” 。
悲观锁(Pessimistic Locking )
悲 观锁,正 如 其名 , 它 指的是对数据被外界 (包括 本 系统当 前的 其他 事 务 , 以及 来自
外部系统 的事务处 理)修改持保守态度,因此 ,在整个数据处 理过程中,将数据处于锁定
状态 。悲观 锁 的实现, 往往 依靠数据库 提 供的 锁机制 (也 只 有数据库层 提 供的 锁机制 才能
真正 保证数据访问的排他 性 , 否 则, 即 使在本系统中 实 现 了 加锁机制 , 也 无法 保证 外部系
统不会修改数据)。
一 个典型的倚赖 数据库 的悲观锁调 用:
select * from account where name=” Erica” for update
这 条 sql 语 句锁定了 account 表中所有符合检 索 条件 ( name= ” Erica ” ) 的记 录 。
本次 事务 提交之 前( 事务 提交 时会释放事务过程 中的锁) ,外界 无法 修改这 些记录 。
Hibernate 的悲观锁 ,也 是基于 数据库的锁机制实 现。
下面 的代码实 现了 对查询记录 的加锁 :
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
String hqlStr =
"from TUser as user where user.name='Erica'";
Query query = session.createQuery(hqlStr);
query.setLockMode("user" ,LockMode.UPGRADE); //加锁
List userList = query.list();//执行查 询, 获取数据
query.setLockMode 对查询语句 中,特定别名所对应 的记录 进行 加锁( 我们为
TUser 类指定了一个 别名“user”), 这里也就是对返回 的所有 user 记录进行 加 锁。
观察 运行期 Hibernate 生成的 SQL 语句:
select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id
as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex
from t_user tuser0_ where (tuser0_.name='Erica' ) for update
这里 Hibernate 通 过使用 数据库 的 for update 子句实现 了 悲观 锁机制。
Hibernate 的加锁模 式有:
Ø LockMode.NONE : 无锁机制。
Ø LockMode.WRITE :Hibernate 在 Insert 和 Update 记录 的时候会 自动
获取。
Ø LockMode.READ : Hibernate 在 读取记录的时 候 会自 动获取 。
以上这三 种锁机制一 般由 Hibernate内部 使 用,如 Hibernate 为 了保证 Update
过程中对象 不会被 外界修改,会 在 save 方法实 现中自动为目标 对象 加上 WRITE 锁。
Ø LockMode.UPGRADE :利 用数据库的 for update 子句 加锁 。
Ø LockMode. UPGRADE_NOWAIT :Oracle 的特定实现,利用 Oracle 的 for
update nowait 子句实现加锁 。
上 面 这 两 种 锁机制是我们 在 应 用 层 较 为常用的,加 锁 一 般 通 过以下方法实现:
Criteria.setLockMode
Query.setLockMode
Session.lock
注 意,只 有在 查询开 始之 前( 也 就是 Hiberate 生成 SQL 之 前)设 定 加锁 , 才 会
真正 通过 数据库 的锁机制进行 加 锁处 理 , 否 则 ,数据已经 通 过不包含 for update
子句的 Select SQL 加载 进来 ,所谓数据库加锁也就 无 从谈起。
乐观锁(Optimistic Locking )
相 对悲观锁而 言 ,乐观锁机制采 取 了更加宽松 的加 锁机制。悲观锁大多数情况下依
靠数据库的锁机制实 现,以保证操作最 大程度的独占性。但随之而来的 就是 数据库
性 能的大 量 开 销, 特别 是对长 事 务而言,这 样的开销往往无法承受 。
如一个金融系统 ,当某 个操作员读取 用 户的数据,并在读 出的用户数据 的基 础上进
行修改时( 如更 改用 户帐 户余额),如果采 用悲观锁 机制,也 就意味着 整个操作 过
程中(从操 作员读 出数据、开始修改直至 提交修改结果的全过程,甚至还包括操作
员 中 途 去煮咖啡的时间),数据库记 录 始终处于加 锁状态 ,可以想 见,如果 面对 几
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
百 上千个 并 发,这样 的 情况 将 导致怎样的后 果。
乐 观 锁机制在一定程度上解决 了这个 问题。乐观锁 ,大多 是基于 数据 版本
(Version)记录机制实现。何谓数据版本?即 为数据增加一 个版本标识 ,在基 于
数据库表的版本 解决方案 中,一般 是 通过为 数据库表增加一个“version ”字 段 来
实 现。
读取出数据时,将此 版本号 一同 读出,之 后更新时,对此 版本号 加一。此 时,将提
交 数据的版本数据与 数 据库 表对应记 录 的 当 前版本 信息 进 行 比对,如果提交 的 数据
版本号大于 数据库表当 前版本号 ,则予以 更新,否 则认为是过期 数据 。
对于上 面 修改用户帐 户信息 的例子而言 ,假设数据库 中帐户信息 表中有一个
version 字段,当 前 值为 1;而当前帐 户余额字 段( balance)为 $100。
1 操 作员 A 此 时将其读出( version=1) , 并从其帐 户余额中 扣除 $50
( $100-$50) 。
2 在操 作员 A 操作的过程中,操 作员 B 也读 入此 用 户 信息( version=1),并
从 其帐 户 余额中 扣 除$20 ( $100-$20) 。
3 操作员 A 完成 了修改工 作,将数据 版本号 加一( version=2),连同帐户扣
除 后余额 ( balance=$50 ) ,提交至数据库更新,此 时由 于提 交 数据 版本 大
于数据库记 录当前版本, 数据被 更新, 数据库记 录 version 更新为 2。
4 操作员 B 完成了操 作, 也 将版本号 加一 ( version=2 )试图向数据库提 交 数
据(balance=$80),但此时比 对数据库记 录版本时发现,操 作 员 B 提交 的
数据版本号为 2 , 数据库记 录当 前版本也为 2 ,不满足“提交 版本必须大于记
录当前版本才 能执 行 更新“的乐观锁 策略 ,因此, 操作员 B 的提交 被驳回。
这样,就 避免了操作员 B 用基于 version=1 的旧 数据修改的 结果覆盖操 作
员 A 的操 作 结果的可能 。
从上面的例子 可 以 看出,乐观锁机制避 免了长事务 中的数据库加锁开销(操作员 A
和 操 作 员 B 操 作 过程 中, 都没有对 数据库数据加 锁 ),大大提升了 大 并发量 下的 系
统整体性 能表现。
需 要注 意的是, 乐观锁机制往往基于 系统 中的 数据 存储 逻辑,因此也具备一 定 的局
限性,如在上例 中,由于乐观 锁机制 是在我们 的系统 中实 现,来 自外 部系统的用户
余 额更新操作不受 我们 系统的控制 , 因此可能 会 造 成脏 数据 被 更新到数据库中。在
系统设计阶段,我们应该充分考虑到这 些情况出现的可能性 ,并进行 相应调整(如
将 乐观锁 策 略 在 数据库存储 过程 中 实 现,对 外 只 开 放 基 于 此 存储过程 的 数据更新途
径,而不 是将 数据库表 直接对外公 开)。
Hibernate 在其数据访问引擎 中 内置了乐观锁实现。如果 不 用考虑 外 部系统对 数
据库的 更新操作,利 用 Hibernate 提供 的透 明化乐观锁实 现,将 大大提升我们 的
生产力。
Hibernate 中可以通 过 class 描述符的 optimistic-lock 属性结合 version
描述符指定。
现在,我们为 之前 示例 中的 TUser 加上乐观 锁机制。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
1. 首先为 TUser 的 class 描述符添加 optimistic-lock 属性:
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
optimistic-lock="version"
>
……
</class >
</hibernate-mapping >
optimistic-lock 属性有如下可选取值 :
Ø none
无 乐观锁
Ø version
通 过版本机制实 现 乐观锁
Ø dirty
通 过检查发 生 变动过的属性实现乐观锁
Ø all
通 过检查所有 属性 实现乐观锁
其中通 过 version 实 现的乐观锁机制是 Hibernate 官方 推荐 的乐观 锁实 现,同时 也
是 Hibernate 中, 目前唯一在 数据对 象 脱离 Session 发生 修改的 情况 下 依 然 有效的 锁机
制。 因此,一 般情况下, 我们 都选择 version 方式作为 Hibernate 乐观锁实现 机制。
2. 添加一个 Version 属性描述符
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
optimistic-lock="version"
>
<id
name="id"
column="id"
type="java.lang.Integer"
>
<generator class="native">
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
</generator >
</id >
<version
column="version"
name="version"
type="java.lang.Integer"
/>
……
</class >
</hibernate-mapping >
注意
version
这里 我们声明 了 一个 version 属性 ,用 于 存 放 用 户 的版本 信息 , 保存在 TUser 表的
version 字段中。
此 时如果 我们 尝试编写一段代码, 更新 TUser 表中记 录数据 ,如:
Criteria criteria = session.createCriteria(TUser.class );
criteria.add(Expression.eq("name" ,"Erica" ));
List userList = criteria.list();
TUser user =(TUser)userList.get(0);
Transaction tx = session.beginTransaction();
user.setUserType(1); //更新 UserType字段
tx.commit();
每次 对 TUser 进行更新 的时 候 ,我们可以 发现, 数据库 中的 version 都在 递 增。
而如果 我们尝 试在 tx.commit 之 前,启动另外 一个 Session ,对名为 Erica 的用
户进 行操作, 以模拟并发 更新时的情形 :
节点必须出现在ID节点之后。
Session session= getSession();
Criteria criteria = session.createCriteria(TUser.class );
criteria.add(Expression.eq("name" ,"Erica" ));
Session session2 = getSession();
Criteria criteria2 = session2.createCriteria(TUser.class );
criteria2.add(Expression.eq("name" ,"Erica" ));
List userList = criteria.list();
List userList2 = criteria2.list();
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
TUser user =(TUser)userList.get(0);
TUser user2 =(TUser)userList2.get(0);
Transaction tx = session.beginTransaction();
Transaction tx2 = session2.beginTransaction();
user2.setUserType(99);
tx2.commit();
user.setUserType(1);
tx.commit();
执 行以上代码, 代码 将在 tx.commit() 处 抛出 StaleObjectStateException 异
常, 并指 出版本检查失败 ,当 前事务正 在试图 提交 一个过期 数据。通过捕捉 这个异 常,我
们 就可 以 在乐观锁 校 验 失败时进行相应处 理 。
Hibernate 分页
数据 分页显示 ,在 系统 实现中往往带来 了 较大的工 作 量 ,对于基 于 JDBC 的程序 而 言 ,
不同数据库提供的分页 (部分读取 )模式往往各 不相同, 也 带来了 数据库间可移植性 上的
问题。
Hibernate 中,通过对 不同数据库的 统一 接口设计,实现了透 明化 、通用化 的分页实
现 机制 。
我们可以通 过 Criteria.setFirstResult 和 Criteria.setFetchSize 方法设
定分页范围 ,如:
Criteria criteria = session.createCriteria(TUser.class );
criteria.add(Expression.eq("age" ,"20" ));
//从检 索结果中获取第 100条记录开始 的 20条记录
criteria.setFirstResult(100);
criteria.setFetchSize(20);
同 样,Query 接 口 也 提供 了 与其一致 的方 法 。
Hibernate 中,抽象类 net.sf.hibernate.dialect 指定了所有底层 数据库的对
外 统一 接 口。通过针对 不 同 数据库 提供 相应 的 dialect 实 现, 数据库之间的差异 性 得以消
除,从而为上 层 机制 提供 了透明的、数据库 无关 的存储层基础。
对 于 分 页机制而言 , dialect 中定义 了 一 个 方 法 如下:
Public String getLimitString(
String querySelect,
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
boolean hasOffset
)
此 方法 用 于在现有 Select 语句 基础上 ,根 据 各 数据库 自身特性 , 构 造对 应 的记录返
回限定子句。如 MySQL 中对应 的记录 限定子句 为 Limit,而 Oracle 中,可通 过 rownum
子句实现。
我们来看 MySQLDialect 中的 getLimitString 实 现:
public String getLimitString(String sql, boolean hasOffset) {
return new StringBuffer( sql.length()+20 )
.append(sql)
.append( hasOffset ? " limit ?, ?" : " limit ?" )
.toString();
}
从上 面可 以 看到 ,MySQLDialect.getLimitString 方法 的 实 现 实际上是在 给 定的
Select 语句 后追加 MySQL 所提供 的专 有 SQL 子句 limit 来实现。
下 面 是 Oracle9Dialect 中的 getLimitString 实 现, 其 中 通过 Oracle 特 有的
rownum 子句实现 了数据的部分读取 。
public String getLimitString(String sql, boolean hasOffset)
{
StringBuffer pagingSelect =
new StringBuffer( sql.length()+100 );
if (hasOffset) {
pagingSelect.append(
"select * from ( select row_.*, rownum rownum_ from ( "
);
}else {
pagingSelect.append("select * from ( " );
}
pagingSelect.append(sql);
if (hasOffset) {
pagingSelect.append(
" ) row_ where rownum <= ?) where rownum_ > ?"
);
}else {
pagingSelect.append(" ) where rownum <= ?" );
}
return pagingSelect.toString();
}
大多 数主 流 数据库都提 供 了数据部分读取机制 , 而 对 于 某些没 有提 供 相应机制的 数据
库而 言,Hibernate 也通 过其他 途径实现了 分页 ,如通过 Scrollable ResultSet ,如
果 JDBC 不支 持 Scrollable ResultSet , Hibernate 也会自动通过 ResultSet 的
next 方法 进行记录定 位 。这样 , Hibernate 通过底层对分页机制的良 好 封装 , 使得开发
人员无需 关心 数据分页 的细节实 现,将 数据逻辑和存储 逻辑分离开来 ,在提高 生产效率的
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
同时,也大大 加强了系统在不同 数据库 平台 之间 的可移植性 。
Cache 管理
Cache 往往是提 高 系统性 能的最重要的手 段 。在 笔 者 记 忆 中, DOS 时代 SmartDrv 2所
带来的磁盘读写性能提 升还 历历在目(记得 95年时安装 Windowns 3.0,在 没 有 SmartDrv
常驻内存的情况下,大概需要 15 分 钟左右,而加载了 SmartDrv,只需要 2 分钟即 可完成
整个 安装 过程 )。
Cache 对于大量倚赖 数据 读取操作的系统而 言(典型的,如 114 查号系统)尤为重 要,
在 大并 发 量 的 情况下,如果每次程 序 都需 要向 数据库直接做查询操作,所带来 的 性能开销
显而易见,频繁 的网络传输、 数据库磁盘的 读写 操作(大多数数据库本身也 有 Cache,但
即使如 此,访 问数据库本身 的开销也极为可 观), 这些都大大降低了系统的整 体性能 。
此时,如果能把 数据在本地内存 中保留 一个 镜像,下 次访问时只 需从内存 中 直接获取,
那么 显然可以 带 来 显 著的性能 提 升( 可 能 是 几倍,甚至 几 十倍的整体读取性能提 升 ).
引入 Cache 机制的难点 是如何保证内 存中数据 的有效性 ,否则脏 数据的出现将给 系统
带 来难 以 预知的严重 后 果。
Hibernate 中实 现了良 好的 Cache 机制,我们 可以借 助 Hibernate 内部的 Cache
迅速 提高系统数据读取 性 能 。
需要 注意的 是 :Hibernate 做为一 个 应用级的 数据 访问层 封装,只能在 其 作用 范围 内
保持 Cache 中 数据的的有效 性, 也就是说,在我们的系统与 第三 方 系统共享 数据库的 情况
下, Hibernate 的 Cache 机制 可 能失效。
Hibernate 在本地 JVM 中维护了一个缓冲池 ,并将 从数据库获得的 数据保存 到池中
以供 下次重复 使 用(如果在 Hibernate 中 数据 发生 了 变动,Hibernate 同样 也会 更新 池
中的数据 版本) 。
此 时,如果有第三 方 系统 对 数据库进行了 更 改,那么 , Hibernate 并不知道 数据库 中
的数据已经发 生了变 化,也就是说,池 中的 数据还 是修改之前的版本,下次读取时,
Hibernate 会将 此 数据返回给 上 层代码 , 从 而导 致潜 在的问题。
外部系统的定 义, 并非限于本 系统之外的第三 方 系统,即使 在本 系统中,如果 出现了
绕 过 Hibernate 数据存储机制的 其 他 数据存取手 段 , 那么 Cache 的有 效 性也 必须 细加 考
量。如,在同 一 套系统 中,基于 Hibernate 和基于 JDBC 的两 种数据访问 方式并 存,那么
通过 JDBC 更新数据库的时候 ,Hibernate 同样无 法获知数据更新的情况 , 从而 导致脏 数
据的 出现。
基于 Java 的 Cache 实现,最简单的莫过于 HashTable, hibernate 提供了基 于
Hashtable 的 Cache 实现机制,不过 ,由 于其性 能 和 功能上的局 限 ,仅 供开发调试 中 使
用。同 时,Hibernate 还提供 了面 向第三 方 Cache 实 现的接口 ,如 JCS、EHCache、
OSCache、 JBoss Cache、 SwarmCache 等。
Hibernate 中的 Cache 大致分 为两 层,第一层 Cache 在 Session 实现,属于 事务
级 数据缓冲 ,一旦 事 务 结 束, 这 个 Cache 也就失效。此层 Cache 为内 置 实 现,无需 我们
2
DOS 下的磁盘读写 缓冲程序
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
进行干 涉 。
第二层 Cache ,是 Hibernate 中对其实例范围 内的 数据 进行缓存 的管理容器。也是
这里 我们讨论的 主 题。
Hibernate 早期版本中采 用了 JCS( Java Caching System - Apache Turbine
项目 中的一个子 项目 )作为默认 的第 二层 Cache 实 现。由 于 JCS 的发 展停顿,以 及 其内 在
的一些问题(在某 些情况 下,可 能导 致 内 存 泄 漏 以 及 死 锁 ) ,新 版本的 Hibernate 已经将
JCS 去除,并用 EHCache 作为其默认 的第 二级 Cache 实现。
相对 JCS, EHCache 更加稳定 , 并具备 更 好的缓存 调度性 能 ,缺陷是目 前还无法做到
分 布 式 缓存 ,如果 我们 的 系统 需 要 在 多台 设 备上 部 署 , 并共享同 一 个数据库 , 必须使用支
持分布式缓存的 Cache 实现( 如 JCS、 JBossCache)以避免 出现不同 系统实例之间缓 存
不一致 而导致脏 数据的情况。
Hibernate 对 Cache 进行了良 好 封装,透明化 的 Cache 机制使得我们在上层结构 的
实现中无需面对繁琐 的 Cache 维护细节 。
目前 Hibernate 支持的 Cache 实 现有:
名称 类 集群支持
HashTable
EHCache
OSCache
SwarmCache
JBossCache
其 中 SwarmCache 和 JBossCache 均提供了 分 布 式 缓存实 现 ( Cache 集群)。
(注:最新 版本的 OSCache 也提供 了分布 式实 现。 )
其 中 SwarmCache 提 供 的是 invalidation 方式的分布 式 缓存 , 即当集群中的某个
节点更新 了缓存中的数据 , 即通知集群中的其他 节点将 此数据废 除, 之后 各个 节点需要用
到这 个数据 的时候 , 会重 新从数据库 中读 入并 填充 到 缓存中。
而 JBossCache 提供的是 Reapplication 式的缓冲 ,即如果集群 中某个 节点的数据
发 生 改 变 , 此节点会将发 生 改 变 的 数据 的最 新 版本 复制到 集 群 中的 每个节点 中 以保持 所有
节点状态一 致。
使 用第二 层 Cache,需 要 在 hibernate.cfg.xml 配置以 下参数( 以 EHCache 为例):
net.sf.hibernate.cache.HashtableCacheP
rovider
net.sf.ehcache.hibernate.Provider N Y
net.sf.hibernate.cache.OSCacheProvider N Y
net.sf.hibernate.cache.SwarmCachePro
vider
net.sf.hibernate.cache.TreeCacheProvid
er
N Y
Y
Y
查询缓冲
<hibernate-configuration >
<session-factory >
……
<property name="hibernate.cache.provider_class">
net.sf.ehcache.hibernate.Provider
</property >
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
……
</session-factory >
</hibernate-configuration >
另外 还需 要 针对 Cache 实 现本 身 进 行 配置 ,如 EHCache 的 配置 文件:
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000" //Cache中最大允 许保存 的数据数量
eternal="false" //Cache中数据是否为 常量
timeToIdleSeconds="120" //缓存数据钝化时间
timeToLiveSeconds="120" //缓存数据的 生存时间
overflowToDisk="true" //内存 不足时,是否启 用磁盘缓存
/>
</ehcache >
其 中“//”开始的 注 释是 笔 者追 加, 实际配置 文件中 不 应出 现。
之后,需要在 我们的映射 文件中 指定各个映射 实体的 Cache 策略:
<class name=" org.hibernate.sample.TUser" .... >
<cache usage="read-write"/>
....
<set name="addresses" .... >
<cache usage="read-only"/>
....
</set >
</class >
缓冲 描述符 cache 可用于描述 映射类和 集 合属性。
上 例 中,Class 节点 下的 cache 描述符指定了针 对类 TUser 的 缓存 策 略 为
“read-write”,即缓冲 中的 TUser 实例 为可读 可写, 而集合属性 addresses 的缓存
策 略为只读。
cache usage 可选值 有以 下几种 :
1. read-only
只读 。
2. read-write
可 读 可 写 。
3. nonstrict-read-write
如果程序 对并 发数据 修改要 求不 是非常严 格 ,只 是偶尔 需要更新数据 ,可以采 用
本选项 ,以减少 无谓 的检查,获得 较 好的性能。
4. transactional
事务性 cache 。在事务性 Cache 中,Cache 的 相关 操作也 被添加到事 务之中,
如果由于某种 原因导致 事务失败 ,我们 可以连同缓冲池 中的数据 一同回滚到 事务
开 始之前的状态。 目 前 Hibernate 内置的 Cache 中, 只 有 JBossCache 支持
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
事 务性 的 Cache 实 现。
不同的 Cache 实现,支持 的 usage 也各不相 同:
名称
HashTable Y Y Y
EHCache Y Y Y
OSCache
SwarmCache
JBossCache
配置 好 Cache 之后,Hibernate 在运行 期 会 自 动应 用 Cache 机制,也就是说,我们
对 PO 的 更新 ,会自动 同步到 Cache 中去 ,而数据的读取 ,也会自动 化的优 先从 Cache 中
获取 ,对于上 层 逻辑代码而 言 ,有 没 有 应 用 Cache 机制 ,并没 有 什 么影响 。
需要注 意的是 Hibernate 的 数据库查询 机制 。我们从 查询结 果中取出数据 的时候 ,
用的最多 的是两个 方法:
Query.list();
Query.iterate();
对 于 list 方 法而言 , 实际上 Hibernate 是通过 一条 Select SQL 获取所有的 记 录 。
并将其读 出,填 入到 POJO 中返回 。
而 iterate 方法 , 则 是 首 先 通 过 一条 Select SQL 获取所有符合查询 条件的 记录的
id,再 对这个 id 集合进行循环操 作,通过 单独 的 Select SQL 取出 每个 id 所对应 的记
录 ,之 后 填 入 POJO 中返回 。
也就是说,对于 list 操作,需要一条 SQL 完成。而 对于 iterate 操作,需要 n+1
条 SQL 。
看上 去 iterate 方 法似 乎 有些多余 , 但在不同 的 情况 下 确依然 有 其独特的功效 ,如对
海量数据的查询 ,如果用 list 方法 将结 果集 一 次取出,内 存的开销 可能无法承受 。
另一方 面,对 于 我们现在的 Cache 机制 而言,list 方法 将不会从 Cache 中读取数据,
它总 是一次性从数据库 中 直接读 出 所有 符合 条件的 记录 。 而 iterate 方 法因 为 每次根据
id 获取数据,这样的实现机制也就为从 Cache 读取数据提供了 可能 ,hibernate 首先会
根 据这个 id 在本地 Cache 内寻 找 对应的 数据,如果 没找到 , 再去数据库 中 检索 。如果 系
统设计中对 Cache 比较倚 重,则请注 意 编码中这两 种不同方法 的应用组 合,有针 对性 的改
善 代码,最大程度 提 升系统的整 体 性能 表 现。
通观以 上内容,Hibernate 通过对 Cache 的封装 ,对 上 层 逻辑层而言,实现了 Cache
read-only read-write nonstrict-read-write transactional
Y Y Y
Y Y
Y Y
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
的 透 明 化实 现, 程序员编码时 无需 关心 数据 在 Cache 中的 状态和调度 , 从而最 大 化协 调 了
性能和 开发效率 之间的平衡。
Session 管理
无疑 ,Session 是 Hibernate 运作的灵魂 ,作 为 贯穿 Hibernate 应用的关键 ,Session
中包含了 数据库操作 相关的状态 信息。如对 JDBC Connection 的维护,数据实 体的状态维持
等 。
对 Session 进行 有效管理 的意义 ,类似 JDBC 程序 设计 中对于 JDBC Connection 的调
度管 理。有效 的 Session 管 理机制,是 Hibernate 应用 设计的关键 。
大多数情况 下,Session 管理的 目标 聚焦 于通 过合 理的设计,避 免 Session 的频繁创建
和销毁 ,从而避 免大量 的内存 开销 和频繁 的 JVM 垃圾回 收,保证系统 高效平 滑运行。
在各种 Session 管理方案 中, ThreadLocal 模式得到了 大量 使 用。ThreadLocal 是
Java 中一种较为特殊 的 线程 绑定 机制 。 通过 ThreadLocal 存取的 数据 ,总是与当前线 程 相关,
也就是说,JVM 为每 个运行的线 程, 绑 定了私有的本地实例存取空 间 ,从而为多线程环境 常出
现的 并发 访 问问题提 供了 一 种隔离机制。
首先,我们需要知道,SessionFactory 负责创建 Session,SessionFactory 是线程
安 全的,多个并 发 线程可以同 时 访 问一 个 SessionFactory 并从 中 获取 Session 实例。 而
Session 并非线程安全,也就是说,如果多个线 程同时 使用一 个 Session 实例进 行数据 存取,
则 将会 导 致 Session 数据 存取逻辑 混乱。下面 是一 个 典型 的 Servlet, 我们试图 通 过一 个 类
变量 session 实现 Session 的重用,以 避免 每次操作 都要 重新创 建:
public class TestServlet extends HttpServlet {
private Session session;
public void doGet( HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
session = getSession();
doSomething();
session.flush();
}
public void doSomething(){
......//基于 session的存取操作
}
}
代码看上去正 确无误,甚至 在我们 单机测试的时 候可能也 不会 发 生什么问题,但 这样的 代
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
码 一旦 编 译部署 到 实际运 行 环境 中,接踵 而来 的莫名 其 妙 的错误很 可 能 会 使得 我们摸不找头脑。
问题出 在哪里 ?
首 先,Servlet 运行是 多 线 程 的, 而 应 用服务器并不会 为 每 个 线程都创建一 个 Servlet
实例,也就是说,TestServlet 在应 用服务器 中只 有一个实例(在 Tomcat 中是这样 ,其他的
应 用服务器 可 能 有 不同 的 实现) , 而 这 个实 例会被许多个线程 并发 调 用, doGet 方法也 将 被不
同的 线程反复 调用,可想而知 ,每次调 用 doGet 方法 ,这个唯 一的 TestServlet 实例的
session 变量都会 被重 置,线 程 A 的 运 行过程中,其 他的 线 程如果也被执行 , 那么 session
的引用将发生 改变 ,之后 线程 A 再 调 用 session ,可能此 时的 session 与其之 前所用的
session 就不再一致 ,显 然,错误也就不期 而 至。
ThreadLocal 的出 现,使得这 个 问题迎刃而解。
我们 对上面 的 例 子 进行一些小小 的修改:
public class TestServlet extends HttpServlet {
private ThreadLocal localSession = new ThreadLocal();
public void doGet( HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
localSession.set(getSession());
doSomething();
session.flush();
}
public void doSomething(){
Session session = (Session)localSession.get();
......//基于 session的存取操作
}
}
可 以看到, localSession 是一 个 ThreadLocal 类型 的对 象,在 doGet 方法 中, 我们
通过其 set 方法 将获取的 session 实例保 存, 而在 doSomething 方法 中, 通过 get 方法取
出 session 实例。
这也就是 ThreadLocal 的独 特之处,它会为每个线 程维护一 个私有的变量空间 。实际上,
其实 现原 理 是在 JVM 中 维护一 个 Map,这个 Map 的 key 就是 当 前的线 程 对象 , 而 value 则是
线程通 过 ThreadLocal.set 方法 保存的对象实例 。当线程调用 ThreadLocal.get 方 法时,
ThreadLocal 会根据当前 线 程对象的引用, 取出 Map 中对应的对 象返回 。
这样,ThreadLocal 通过以各 个线 程对象的引用作为区 分,从而将 不同线程的 变量隔离 开
来。
回到上 面的例子 ,通 过应用 ThreadLocal 机制 ,线程 A 的 session 实例只 能为线 程 A
所用, 同样,其他线程的 session 实例 也各 自从 属 于自 己 的 线 程。这样 , 我们 就实现 了线 程安
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
全的 Session 共享机制 。
Hibernate 官方 开发手册 的示例 中,提供了 一个通 过 ThreadLocal 维护 Session 的好
榜 样:
public class HibernateUtil {
private static SessionFactory sessionFactory;
static {
try {
// Create the SessionFactory
sessionFactory = new
Configuration().configure().buildSessionFactory();
} catch (HibernateException ex) {
throw new RuntimeException(
"Configuration problem: " + ex.getMessage(),
ex
);
}
}
public static final ThreadLocal session = new ThreadLocal();
public static Session currentSession() throws HibernateException
{
Session s = (Session) session.get();
// Open a new Session, if this Thread has none yet
if (s == null ) {
s = sessionFactory.openSession();
session.set(s);
}
return s;
}
public static void closeSession() throws HibernateException {
Session s = (Session) session.get();
session.set(null );
if (s != null )
s.close();
}
}
在 代码中,只要借 助 上 面 这 个 工 具类获取 Session 实例 , 我们就可 以实 现 线 程范围 内 的
Session 共享,从而避 免了 在线程 中 频繁的创建和销毁 Session 实例。 不过注意在 线 程结束
时 关闭 Session。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
同 时值得 一提的是, 新版本的 Hibernate 在处理 Session 的时 候已经内 置了 延迟 加 载 机
制,只有在真正 发生数据库操作 的时 候,才会从数据库连 接池 获取数据库连 接,我们不必过于担
心 Session 的 共享会导致 整个 线 程生 命周 期内数据库连 接 被持 续占用。
上面的 HibernateUtil 类可以应 用 在任何类型 的 Java 程序 中。特别 的,对于 Web 程序
而 言,我们可以 借 助 Servlet2.3 规范 中 新 引 入 的 Filter 机制,轻松实现线 程 生 命周 期 内 的
Session 管理(关于 Filter 的具 体描述 ,请参考 Servlet2.3 规范)。
Filter的生命周期贯穿了其所 覆盖的 Servlet(JSP也可以 看作是一种特殊 的 Servlet)
及其底层对象 。Filter 在 Servlet 被调用之 前 执行,在 Servlet 调用结束之 后结束。因此,
在 Filter 中管理 Session 对于 Web 程序 而言 就显 得水到 渠成。下面 是一个通 过 Filter 进
行 Session 管 理 的典型案例:
public class PersistenceFilter implements Filter
{
protected static ThreadLocal hibernateHolder = new ThreadLocal();
public void doFilter(ServletRequest request, ServletResponse
response, FilterChain chain)
throws IOException, ServletException
{
hibernateHolder.set(getSession());
try
{
……
chain.doFilter(request, response);
……
}
finally
{
Session sess = (Session)hibernateHolder.get();
if (sess != null )
{
hibernateHolder.set(null );
try
{
sess.close();
}
catch (HibernateException ex) {
throw new ServletException(ex);
}
}
}
}
……
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
}
通 过在 doFilter 中 获取和关闭 Session , 并在周期 内运 行 的所有对 象( Filter 链中其
余的 Filter ,及其覆盖 的 Servlet 和其他 对象)对此 Session 实例 进行重 用,保证了 一个
Http Request 处理过程中只占 用一 个 Session ,提高了整 体 性能 表现。
在实际设计 中,Session 的重 用做到线 程级别一般 已经足够,企 图通 过 HttpSession 实
现用户级 的 Session 重 用反而 可能导致 其他的问题。凡事不能过火,Session 重用也 一样。J
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
Hibernate in Spring
下面主要就Hibernate在 Spring中的应用加以介绍,关于Spring Framework请参 见
笔者 另外一篇 文 献:
《 Spring开发指南》 http://www.xiaxin.net/Spring_Dev_Guide.rar
Spring的参数 化 事务管理功能相 当 强大,笔 者建议 在基于Spring Framework的应 用
开发中,尽量 使用 容器管理事务 ,以获 得数据逻辑代码的最佳 可读性 。下面 的介绍 中,将 略
过代码控制的事 务管理部分,而将重点放 在参数 化 的容器事务管 理应用。代码级事务管理实
现原 理 请 参 见 《Spring开发指南》中的相关内容 。
首先,针 对Hibernate, 我们 需要 进行 如下配置 :
Hibernate-Context.xml:
<beans>
<bean id ="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>net.sourceforge.jtds.jdbc.Driver </ value>
</property >
<property name="url">
<value>jdbc:jtds:sqlserver://127.0.0.1:1433/Sample </ value>
</property >
<property name="username">
<value>test </ value>
</property >
<property name="password">
<value>changeit </ value>
</property >
</bean >
<bean id ="sessionFactory"
class= "org.springframework.orm.hibernate.LocalSessionFactoryBean"
>
<property name="dataSource">
<ref local="dataSource" />
</property >
<property name="mappingResources">
<list>
<value>net/xiaxin/dao/entity/User.hbm.xml </ value>
</list >
</property >
<property name="hibernateProperties">
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
<props>
<prop key="hibernate.dialect">
net.sf.hibernate.dialect.SQLServerDialect
</prop >
<prop key="hibernate.show_sql">
true
</prop >
</props >
</property >
</bean >
<bean id ="transactionManager"
class= "org.springframework.orm.hibernate.HibernateTransactionMana
ger">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property >
</bean >
<bean id ="userDAO" class ="net.xiaxin.dao.UserDAO">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property >
</bean >
<bean id ="userDAOProxy"
class= "org.springframework.transaction.interceptor.TransactionPro
xyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property >
<property name="target">
<ref local="userDAO" />
</property >
<property name="transactionAttributes">
<props>
<prop key="insert*"> PROPAGATION_REQUIRED</ prop>
<prop key="get*"> PROPAGATION_REQUIRED,readOnly</ prop>
</props >
</property >
</bean >
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
</beans >
其中:
1. SessionFactory的配置
Hibernate中通过 SessionFactory创建和 维护 Session。 Spring对
SessionFactory的配置也进行 了整合 ,无需再通过 Hibernate.cfg.xml对
SessionFactory进行设定。
SessionFactory节点的 mappingResources属性包含了 映射文件的路径 ,list
节点 下可 配置 多个映射 文件。
hibernateProperties节点则容纳 了所有的 属性配置。
可以 对应 传统 的Hibernate.cfg.xml 文件结构 对这里 的SessionFactory配置
进行解读 。
2. 采用面 向 Hibernate的TransactionManager实 现:
org.springframework.orm.hibernate.HibernateTransactionManag
er
这里引 入了一个 非常简单的 库表:Users,建立 如下映射类 :
User.java:
/**
* @hibernate.class table="users"
*/
public class User {
public Integer id;
public String username;
public String password;
/**
* @hibernate.id
* column="id"
* type="java.lang.Integer"
* generator- class="native"
*/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this .id = id;
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
}
/**
* @hibernate.property column="password" length="50"
*/
public String getPassword() {
return password;
}
public void setPassword(String password) {
this .password = password;
}
/**
* @hibernate.property column="username" length="50"
*/
public String getUsername() {
return username;
}
public void setUsername(String username) {
this .username = username;
}
}
上面的 代码中,通过 xdoclet 指定了类 /表;属性/字段的 映射关系,通过 xdoclet ant
task 我们可以 根据代码生成对 应的 user.hbm.xml 文件。
下面 是生成 的 user.hbm.xml:
<hibernate-mapping>
<class
name="net.xiaxin.dao.entity.User"
table="users"
dynamic-update="false"
dynamic-insert="false"
>
<id
name="id"
column="id"
type="java.lang.Integer"
>
<generator class="native">
</generator >
</id >
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
<property
name="password"
type="java.lang.String"
update="true"
insert="true"
access="property"
column="password"
length="50"
/>
<property
name="username"
type="java.lang.String"
update="true"
insert="true"
access="property"
column="username"
length="50"
/>
</class >
</hibernate-mapping >
UserDAO.java:
public class UserDAO extends HibernateDaoSupport implements IUserDAO
{
public void insertUser(User user) {
getHibernateTemplate().saveOrUpdate(user);
}
}
看到这段 代码想必会有 点诧 异, 似 乎太 简单了一 点……, 不过 这已经足够。短短 一 行
代码我们已经实现 了与 上一章中示例相同 的功能 ,这也正体现 了Spring+Hibernate
的威力 所在。
上 面的UserDAO实现了 自 定义 的 IUserDAO 接口, 并 扩展了 抽 象类:
HibernateDaoSupport
HibernateSupport实现了 HibernateTemplate和 SessionFactory实例的关联。
HibernateTemplate对 Hibernate Session操 作进行了封装 ,而
HibernateTemplate.execute方法则 是一封装机制 的核心 ,感兴趣的读 者可 以研究一
下 其实现 机制 。
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
借 助HibernateTemplate我们可以脱离每次数据 操 作必须首 先 获得Session实例、启
动事 务、提交/ 回滚 事务以 及烦杂的try/catch/finally等繁琐操 作。从而获得以上 代码
中精干集 中的逻辑呈现效 果。
对比 下面这段 实 现了同样功能的 Hibernate原生代码,想必 更有体会 :
Session session
try {
Configuration config = new Configuration().configure();
SessionFactory sessionFactory =
config.buildSessionFactory();
session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = new User();
user.setName("erica" );
user.setPassword("mypass" );
session.save(user);
tx.commit();
} catch (HibernateException e) {
e.printStackTrace();
tx.rollback();
}finally{
session.close();
}
附上例 的测试代码:
InputStream is = new FileInputStream("Hibernate-Context.xml" );
XmlBeanFactory factory = new XmlBeanFactory(is);
IUserDAO userDAO = (IUserDAO)factory.getBean("userDAOProxy" );
User user = new User();
user.setUsername("erica" );
user.setPassword("mypass" );
userDAO.insertUser(user);
September 2, 2004 So many open source projects. Why not Open your Doc uments?
Hibernate Developer’ s Guide Version 1.0
编后赘言
Hibernate 是一个优秀 的 ORM 实现,不过请注 意, 它只是一个 ORM
实现 而已, 也 不能保证 是最优秀 的。
笔者 使用 过的 ORM 实 现中,Apache OJB、Oracle TopLink、 IBatis
和 Jaxor 都 给笔者留 下了深刻映 像 ,是 否选择 Hibernate 作为持久层实现,
需要结合实际 情况考虑(在很多 情况 下,比如对遗留 系统的改造 项目中、
ibatis 可能更 加 合适 ) 。
合理的 设计,冷静 的取舍 是考量 一 个系统架 构师功 底的最 实际的标
准 。常 在网 上看到 对 Hibernate 以及 其 他 一些 项目或者 框架标 准 的狂热
鼓吹,不 禁想起自己 三年前 逢项目必定 为 EJB 摇旗呐喊的样子 。
设计 需要 用时 间来 沉淀,建筑、服装都是如此 , 软件产品 也 一 样……
September 2, 2004 So many open source projects. Why not Open your Doc uments?