Hibernate手册(转载)

HIBERNATE - 符合Java习惯的关系数据库持久化

Hibernate参考文档

3.0.4


目录

前言
1. 翻译说明 2. 版权声明
1. 在Tomcat中快速上手
1.1. 开始Hibernate之旅 1.2. 第一个持久化类 1.3. 映射cat 1.4. 与Cat同乐 1.5. 结语
2. Hibernate入门
2.1. 前言 2.2. 第一部分 - 第一个Hibernate程序
2.2.1. 第一个class 2.2.2. 映射文件 2.2.3. Hibernate配置 2.2.4. 用Ant编译 2.2.5. 安装和帮助 2.2.6. 加载并存储对象
2.3. 第二部分 - 关联映射
2.3.1. 映射Person类 2.3.2. 一个单向的Set-based关联 2.3.3. 使关联工作 2.3.4. 值类型的集合 2.3.5. 双向关联 2.3.6. 使双向关联工作
2.4. 总结
3. 体系结构(Architecture)
3.1. 概况(Overview) 3.2. 实例状态 3.3. JMX整合 3.4. 对JCA的支持
4. 配置
4.1. 可编程的配置方式 4.2. 获得SessionFactory 4.3. JDBC连接 4.4. 可选的配置属性
4.4.1. SQL方言 4.4.2. 外连接抓取(Outer Join Fetching) 4.4.3. 二进制流 (Binary Streams) 4.4.4. 二级缓存与查询缓存 4.4.5. 查询语言中的替换 4.4.6. Hibernate的统计(statistics)机制
4.5. 日志 4.6. 实现NamingStrategy 4.7. XML配置文件 4.8. J2EE应用程序服务器的集成
4.8.1. 事务策略配置 4.8.2. JNDI绑定的SessionFactory 4.8.3. JTA和Session的自动绑定 4.8.4. JMX部署
5. 持久化类(Persistent Classes)
5.1. 一个简单的POJO例子
5.1.1. 为持久化字段声明访问器(accessors)和是否可变的标志(mutators) 5.1.2. 实现一个默认的(即无参数的)构造方法(constructor) 5.1.3. 提供一个标识属性(identifier property)(可选) 5.1.4. 使用非final的类 (可选)
5.2. 实现继承(Inheritance) 5.3. 实现equals()和hashCode() 5.4. 动态模型(Dynamic models)
6. 对象/关系数据库映射基础(Basic O/R Mapping)
6.1. 映射定义(Mapping declaration)
6.1.1. Doctype 6.1.2. hibernate-mapping 6.1.3. class 6.1.4. id
6.1.4.1. Generator 6.1.4.2. 高/低位算法(Hi/Lo Algorithm) 6.1.4.3. UUID算法(UUID Algorithm ) 6.1.4.4. 标识字段和序列(Identity columns and Sequences) 6.1.4.5. 程序分配的标识符(Assigned Identifiers) 6.1.4.6. 触发器实现的主键生成器(Primary keys assigned by triggers)
6.1.5. composite-id 6.1.6. 鉴别器(discriminator) 6.1.7. 版本(version)(可选) 6.1.8. timestamp (optional) 6.1.9. property 6.1.10. 多对一(many-to-one) 6.1.11. 一对一 6.1.12. 组件(component), 动态组件(dynamic-component) 6.1.13. properties 6.1.14. 子类(subclass) 6.1.15. 连接的子类(joined-subclass) 6.1.16. 联合子类(union-subclass) 6.1.17. 连接(join) 6.1.18. 键(key) 6.1.19. 字段和规则元素(column and formula elements) 6.1.20. 引用(import) 6.1.21. any
6.2. Hibernate 的类型
6.2.1. 实体(Entities)和值(values) 6.2.2. 基本值类型 6.2.3. 自定义值类型
6.3. SQL中引号包围的标识符 6.4. 其他元数据(Metadata)
6.4.1. 使用 XDoclet 标记 6.4.2. 使用 JDK 5.0 的注解(Annotation)
7. 集合类(Collections)映射
7.1. 持久化集合类(Persistent collections) 7.2. 集合映射( Collection mappings )
7.2.1. 集合外键(Collection foreign keys) 7.2.2. 集合元素(Collection elements) 7.2.3. 索引集合类(Indexed collections) 7.2.4. 值集合于多对多关联(Collections of values and many-to-many associations) 7.2.5. 一对多关联(One-to-many Associations)
7.3. 高级集合映射(Advanced collection mappings)
7.3.1. 有序集合(Sorted collections) 7.3.2. 双向关联(Bidirectional associations) 7.3.3. 三重关联(Ternary associations) 7.3.4. 使用<idbag>
7.4. 集合例子(Collection example)
8. 关联关系映射
8.1. 介绍 8.2. 单向关联(Unidirectional associations)
8.2.1. 多对一(many to one) 8.2.2. 一对一(one to one) 8.2.3. 一对多(one to many)
8.3. 使用连接表的单向关联(Unidirectional associations with join tables)
8.3.1. 一对多(one to many) 8.3.2. 多对一(many to one) 8.3.3. 一对一(one to one) 8.3.4. 多对多(many to many)
8.4. 双向关联(Bidirectional associations)
8.4.1. 一对多(one to many) / 多对一(many to one) 8.4.2. 一对一(one to one)
8.5. 使用连接表的双向关联(Bidirectional associations with join tables)
8.5.1. 一对多(one to many) /多对一( many to one) 8.5.2. 一对一(one to one) 8.5.3. 多对多(many to many)
9. 组件(Component)映射
9.1. 依赖对象(Dependent objects) 9.2. 在集合中出现的依赖对象 9.3. 组件作为Map的索引(Components as Map indices ) 9.4. 组件作为联合标识符(Components as composite identifiers) 9.5. 动态组件 (Dynamic components)
10. 继承映射(Inheritance Mappings)
10.1. 三种策略
10.1.1. 每个类分层结构一张表(Table per class hierarchy) 10.1.2. 每个子类一张表(Table per subclass) 10.1.3. 每个子类一张表(Table per subclass),使用辨别标志(Discriminator) 10.1.4. 混合使用“每个类分层结构一张表”和“每个子类一张表” 10.1.5. 每个具体类一张表(Table per concrete class) 10.1.6. Table per concrete class, using implicit polymorphism 10.1.7. 隐式多态和其他继承映射混合使用
10.2. 限制
11. 与对象共事
11.1. Hibernate对象状态(object states) 11.2. 使对象持久化 11.3. 装载对象 11.4. 查询
11.4.1. 执行查询
11.4.1.1. 迭代式获取结果(Iterating results) 11.4.1.2. 返回元组(tuples)的查询 11.4.1.3. 标量(Scalar)结果 11.4.1.4. 绑定参数 11.4.1.5. 分页 11.4.1.6. 可滚动遍历(Scrollable iteration) 11.4.1.7. 外置命名查询(Externalizing named queries)
11.4.2. 过滤集合 11.4.3. 条件查询(Criteria queries) 11.4.4. 使用原生SQL的查询
11.5. 修改持久对象 11.6. 修改脱管(Detached)对象 11.7. 自动状态检测 11.8. 删除持久对象 11.9. 在两个不同数据库间复制对象 11.10. Session刷出(flush) 11.11. 传播性持久化(transitive persistence) 11.12. 使用元数据
12. 事务和并发
12.1. Session和事务范围(transaction scopes)
12.1.1. 操作单元(Unit of work) 12.1.2. 应用程序事务(Application transactions) 12.1.3. 关注对象标识(Considering object identity) 12.1.4. 常见问题
12.2. 数据库事务声明
12.2.1. 非托管环境 12.2.2. 使用JTA 12.2.3. 异常处理
12.3. 乐观并发控制(Optimistic concurrency control)
12.3.1. 应用程序级别的版本检查(Application version checking) 12.3.2. 长生命周期session和自动版本化 12.3.3. 脱管对象(deatched object)和自动版本化 12.3.4. 定制自动版本化行为
12.4. 悲观锁定(Pessimistic Locking)
13. 拦截器与事件(Interceptors and events)
13.1. 拦截器(Interceptors) 13.2. 事件系统(Event system) 13.3. Hibernate的声明式安全机制
14. 批量处理(Batch processing)
14.1. 批量插入(Batch inserts) 14.2. 批量更新(Batch updates) 14.3. 大批量更新/删除(Bulk update/delete)
15. HQL: Hibernate查询语言
15.1. 大小写敏感性问题 15.2. from子句 15.3. 关联(Association)与连接(Join) 15.4. select子句 15.5. 聚集函数 15.6. 多态查询 15.7. where子句 15.8. 表达式 15.9. order by子句 15.10. group by子句 15.11. 子查询 15.12. HQL示例 15.13. 批量的UPDATE & DELETE语句 15.14. 小技巧 & 小窍门
16. 条件查询(Criteria Queries)
16.1. 创建一个Criteria 实例 16.2. 限制结果集内容 16.3. 结果集排序 16.4. 关联 16.5. 动态关联抓取 16.6. 查询示例 16.7. 投影(Projections)、聚合(aggregation)和分组(grouping) 16.8. 离线(detached)查询和子查询
17. Native SQL查询
17.1. 创建一个基于SQL的Query 17.2. 别名和属性引用 17.3. 命名SQL查询
17.3.1. 使用return-property来明确地指定字段/别名 17.3.2. 使用存储过程来查询
17.3.2.1. 使用存储过程的规则和限制
17.4. 定制SQL用来create,update和delete 17.5. 定制装载SQL
18. 过滤数据
18.1. Hibernate 过滤器(filters)
19. XML映射
19.1. 用XML数据进行工作
19.1.1. 指定同时映射XML和类 19.1.2. 只定义XML映射
19.2. XML映射元数据 19.3. 操作XML数据
20. 提升性能
20.1. 抓取策略(Fetching strategies)
20.1.1. 操作延迟加载的关联 20.1.2. 调整抓取策略(Tuning fetch strategies) 20.1.3. 单端关联代理(Single-ended association proxies) 20.1.4. 实例化集合和代理(Initializing collections and proxies) 20.1.5. 使用批量抓取(Using batch fetching) 20.1.6. 使用子查询抓取(Using subselect fetching) 20.1.7. 使用延迟属性抓取(Using lazy property fetching)
20.2. 二级缓存(The Second Level Cache)
20.2.1. 缓存映射(Cache mappings) 20.2.2. 策略:只读缓存(Strategy: read only) 20.2.3. 策略:读/写缓存(Strategy: read/write) 20.2.4. 策略:非严格读/写缓存(Strategy: nonstrict read/write) 20.2.5. 策略:事务缓存(transactional)
20.3. 管理缓存(Managing the caches) 20.4. 查询缓存(The Query Cache) 20.5. 理解集合性能(Understanding Collection performance)
20.5.1. 分类(Taxonomy) 20.5.2. Lists, maps 和sets用于更新效率最高 20.5.3. Bag和list是反向集合类中效率最高的 20.5.4. 一次性删除(One shot delete)
20.6. 监测性能(Monitoring performance)
20.6.1. 监测SessionFactory 20.6.2. 数据记录(Metrics)
21. 工具箱指南
21.1. Schema自动生成(Automatic schema generation)
21.1.1. 对schema定制化(Customizing the schema) 21.1.2. 运行该工具 21.1.3. 属性(Properties) 21.1.4. 使用Ant(Using Ant) 21.1.5. 对schema的增量更新(Incremental schema updates) 21.1.6. 用Ant来增量更新schema(Using Ant for incremental schema updates)
22. 示例:父子关系(Parent Child Relationships)
22.1. 关于collections需要注意的一点 22.2. 双向的一对多关系(Bidirectional one-to-many) 22.3. 级联生命周期(Cascading lifecycle) 22.4. 级联与未保存值(Cascades and unsaved-value) 22.5. 结论
23. 示例:Weblog 应用程序
23.1. 持久化类 23.2. Hibernate 映射 23.3. Hibernate 代码
24. 示例:复杂映射实例
24.1. Employer(雇主)/Employee(雇员) 24.2. Author(作家)/Work(作品) 24.3. Customer(客户)/Order(订单)/Product(产品) 24.4. 杂例
24.4.1. "Typed" one-to-one association 24.4.2. Composite key example 24.4.3. Content based discrimination 24.4.4. Associations on alternate keys
25. 最佳实践(Best Practices)

前言

WARNING! This is a translated version of the English Hibernate reference documentation. The translated version might not be up to date! However, the differences should only be very minor. Consult the English reference documentation if you are missing information or encounter a translation error. If you like to contribute to a particular translation, contact us on the Hibernate developer mailing list.

Translator(s): RedSaga Translate Team 满江红翻译团队 <caoxg@yahoo.com>

在今日的企业环境中,把面向对象的软件和关系数据库一起使用可能是相当麻烦、浪费时间的。Hibernate是一个面向Java环境的对象/关系数据库映射工具。对象/关系数据库映射(object/relational mapping (ORM))这个术语表示一种技术,用来把对象模型表示的对象映射到基于SQL的关系模型数据结构中去。

Hibernate不仅仅管理Java类到数据库表的映射(包括Java数据类型到SQL数据类型的映射),还提供数据查询和获取数据的方法,可以大幅度减少开发时人工使用SQL和JDBC处理数据的时间。

Hibernate的目标是对于开发者通常的数据持久化相关的编程任务,解放其中的95%。对于以数据为中心的程序来说,它们往往只在数据库中使用存储过程来实现商业逻辑,Hibernate可能不是最好的解决方案;对于那些在基于Java的中间层应用中,它们实现面向对象的业务模型和商业逻辑的应用,Hibernate是最有用的。不管怎样,Hibernate一定可以帮助你消除或者包装那些针对特定厂商的SQL代码,并且帮你把结果集从表格式的表示形式转换到一系列的对象去。

如果你对Hibernate和对象/关系数据库映射还是个新手,或者甚至对Java也不熟悉,请按照下面的步骤来学习。

  1. 阅读这个30分钟就可以结束的第 1 章 在Tomcat中快速上手,它使用Tomcat。

  2. 阅读第 2 章 Hibernate入门 ,这是一篇较长的指南,包含详细的逐步指导。

  3. 阅读第 3 章 体系结构(Architecture)来理解Hibernate可以使用的环境。

  4. 查看Hibernate发行包中的eg/目录,里面有一个简单的独立运行的程序。把你的JDBC驱动拷贝到lib/目录下,修改一下src/hibernate.properties,指定其中你的数据库的信息。进入命令行,切换到你的发行包的目录,输入ant eg(使用了Ant),或者在Windows操作系统中使用build eg

  5. 把这份参考文档作为你学习的主要信息来源。

  6. 在Hibernate 的网站上可以找到经常提问的问题与解答(FAQ)。

  7. 在Hibernate网站上还有第三方的演示、示例和教程的链接。

  8. Hibernate网站的“社区(Community Area)”是讨论关于设计模式以及很多整合方案(Tomcat, JBoss AS, Struts, EJB,等等)的好地方。

如果你有问题,请使用Hibernate网站上链接的用户论坛。我们也提供一个JIRA问题追踪系统,来搜集bug报告和新功能请求。如果你对开发Hibernate有兴趣,请加入开发者的邮件列表。(Hibernate网站上的用户论坛有一个中文版面,JavaEye也有Hibernate中文版面,您可以在那里交流问题与经验。)

商业开发、产品支持和Hibernate培训可以通过JBoss Inc.获得。(请查阅:http://www.hibernate.org/SupportTraining/)。 Hibernate是一个专业的开放源代码项目(Professional Open Source project),也是JBoss Enterprise Middleware System(JEMS),JBoss企业级中间件系统的一个核心组件。

1. 翻译说明

本文档的翻译是在网络上协作进行的,也会不断根据Hibernate的升级进行更新。提供此文档的目的是为了减缓学习Hibernate的坡度,而非代替原文档。我们建议所有有能力的读者都直接阅读英文原文。若您对翻译有异议,或发现翻译错误,敬请不吝赐教,报告到如下email地址:cao at redsaga.com

Hibernate版本3的翻译由满江红翻译团队(RedSaga Translate Team)集体进行,这也是一次大规模网络翻译的试验。在不到20天的时间内,我们完成了两百多页文档的翻译,这一成果是通过十几位网友集体努力完成的。通过这次翻译,我们也有了一套完整的流程,从初译、技术审核一直到文字审核、发布。我们的翻译团队还会继续完善我们的翻译流程,并翻译其他优秀的Java开源资料,敬请期待。

表 1.  Hibernate v3翻译团队

序号 标题 中文标题 翻译 审校
#1 Quickstart with Tomcat 在Tomcat中快速上手 曹晓钢 zoujm
#2 Architecture 体系结构 Hilton(BJUG) 厌倦发呆
#3 Configuration 配置 Goncha mochow
#4 Persistent Classes 持久化类 曹晓钢 mochow
#5 Basic O/R Mapping 对象/关系数据库映射基础(上) moxie Kingfish
    对象/关系数据库映射基础(下) inter_dudu vincent
#6 Collection Mapping 集合类映射 曹晓钢 robbin
#7 Association Mappings 关联关系映射 Robbin devils.advocate
#8 Component Mapping 组件映射 曹晓钢 Robbin
#9 Inheritance Mappings 继承映射 morning(BJUG) mochow
#10 Working with objects 与对象共事 程广楠 厌倦发呆
#11 Transactions And Concurrency 事务和并发 Robbin mochow
#12 Interceptors and events 继承映射 七彩狼(BJUG) 厌倦发呆
#13 Batch processing 批量处理 Kingfish(BJUG) 厌倦发呆
#14 HQL: The Hibernate Query Language HQL: Hibernate查询语言 郑浩(BJUG) zhengshuai
#15 Criteria Queries 条件查询 nemo(BJUG) zhengshuai
#16 Native SQL Native SQL查询 似水流年 zoujm
#17 Filters 过滤数据 冰云(BJUG) Goncha
#18 XML Mapping XML映射 edward(BJUG) Goncha
#19 Improving performance 性能提升 Wangjinfeng Robbin
#20 Toolset Guide 工具箱指南 曹晓钢 Robbin
#21 Example: Parent/Child 示例:父子关系 曹晓钢 devils.advocate
#22 Example: Weblog Application 示例:Weblog 应用程序 曹晓钢 devils.advocate
#23 Example: Various Mappings 示例:多种映射 shidu(BJUG) 冰云
#24 Best Practices 最佳实践 曹晓钢 冰云

关于我们

满江红.开源, http://www.redsaga.com

从成立之初就致力于Java开放源代码在中国的传播与发展,与国内多个Java团体及出版社有深入交流。坚持少说多做的原则,目前有两个团队,“OpenDoc团队”与“翻译团队”,本翻译文档即为翻译团队作品。OpenDoc团队已经推出包括Hibernate、iBatis、Spring、WebWork的多份开放文档,并于2005年5月在Hibernate开放文档基础上扩充成书,出版了原创书籍:《深入浅出Hibernate》,本书400余页,适合各个层次的Hibernate用户。(http://www.redsaga.com/hibernate_book.html)敬请支持。

北京Java用户组, http://www.bjug.org

Beiing Java User Group,民间技术交流组织,成立于2004年6月。以交流与共享为宗旨,每两周举行一次技术聚会活动。BJUG的目标是,通过小部分人的努力,形成一个技术社群,创建良好的交流氛围,并将新的技术和思想推广到整个IT界,让我们共同进步。

Java视线, http://www.javaeye.com

Java视线在是Hibernate中文论坛(http://www.hibernate.org.cn,Hibernate中文论坛是中国最早的Hibernate专业用户论坛,为Hibernate在中国的推广做出了巨大的贡献)基础上发展起来的Java深度技术网站,目标是成为一个高品质的,有思想深度的、原创精神的Java技术交流网站,为软件从业人员提供一个自由的交流技术,交流思想和交流信息的平台。

2. 版权声明

Hibernate英文文档属于Hibernate发行包的一部分,遵循LGPL协议。本翻译版本同样遵循LGPL协议。参与翻译的译者一致同意放弃除署名权外对本翻译版本的其它权利要求。

您可以自由链接、下载、传播此文档,或者放置在您的网站上,甚至作为产品的一部分发行。但前提是必须保证全文完整转载,包括完整的版权信息和作译者声明,并不能违反LGPL协议。这里“完整”的含义是,不能进行任何删除/增添/注解。若有删除/增添/注解,必须逐段明确声明那些部分并非本文档的一部分。

第 1 章 在Tomcat中快速上手

1.1. 开始Hibernate之旅

这份教程描述如何在Apache Tomcat servlet容器中为web应用程序配置Hibernate 3.0(我们使用Tomcat 4.1版本,与5.0版本差别很小)。Hibernate在大多数主流J2EE应用服务器 的运行环境中都可以工作良好,甚至也可以在独立Java应用程序中使用。在本教程中使用的示例数据库系统是PostgreSQL 7.4,只需要修改Hibernate SQL语言配置与连接属性,就可以 很容易的支持其他数据库了。

第一步,我们必须拷贝所有需要的库文件到Tomcat安装目录中。在这篇教程中,我们使用一个独立的web Context配置(webapps/quickstart)。我们确认全局库文件(TOMCAT/common/lib)和本web应用程序上下文的路径(对于jar来说是webapps/quickstart/WEB-INF/lib,对于class文件来说是webapps/quickstart/WEB-INF/classes)能够被类装载器检索到。我们把这两个类装载器级别分别称做全局类路径(global classpath)和上下文类路径(context classpath)。

现在,把这些库文件copy到两个类路径去:

  1. 把数据库需要的JDBC驱动文件拷贝到全局类路径,这是tomcat捆绑的DBCP连接池所需要的。Hibernate使用JDBC连接数据库方式执行SQL语句,所以你要么提供外部连接池中的连接给Hibernate,或者配置Hibernate自带的连接池(C3PO,Proxool)。对于本教程来说,把pg74jdbc3.jar库文件(支持PostgreSQL 7.4和JDK 1.4)到全局类装载路径下即可。如果你希望使用其他的数据库,拷贝其相应的JDBC 驱动文件)。

  2. 永远不要拷贝任何其他东西到Tomcat的全局类路径下,否则你可能在使用其他一些工具上遇到麻烦,比如log4j, commons-logging等等。 一定要让每个web应用程序使用自己的上下文类路径,就是说把你自己需要的类库拷贝到WEB-INF/lib下去,把配置文件configuration/property等配置文件拷贝到WEB-INF/classes下面去。这两个目录都是当前程序缺省的上下文类路径。

  3. Hibernate本身打包成一个JAR类库。将hibernate3.jar文件拷贝到程序的上下文类路径下,和你应用程序的其他库文件放一起。在运行时,Hibernate还需要一些第三方类库,它们在Hibernate发行包的lib/目录下。参见表 1.1 “ Hibernate 第三方类库 ”。把所需要的第三方库文件也拷贝到上下文类路径下。

表 1.1.  Hibernate 第三方类库

类库 描述
antlr (必需) Hibernate使用ANTLR来产生查询分析器,这个类库在运行环境下时也是必需的。
dom4j (必需) Hibernate使用dom4j解析XML配置文件和XML映射元文件。
CGLIB ,asm(必需) Hibernate在运行时使用这个代码生成库增强类(与Java反射机制联合使用)。
Commons Collections, Commons Logging (必需) Hibernat使用Apache Jakarta Commons项目提供的多个工具类库。
EHCache (必需) Hibernate可以使用不同cache缓存工具作为二级缓存。EHCache是缺省的cache缓存工具。
Log4j (可选) Hibernate使用Commons Logging API,它也可以依次使用Log4j作为底层实施log的机制。如果上下文类目录中存在Log4j库,则Commons Logging使用Log4j和并它在上下文类路径中寻找的log4j.properties文件。你可以使用在Hibernate发行包中包含中的那个示例Log4j的配置文件。这样,把log4j.jar和它的配置文件(位于src/目录中)拷贝到你的上下文类路径下,就可以在后台看到底程序如何运行的。
其他文件是不是必需的? 请察看Hibernate发行包中的 lib/README.txt文件,这是一个Hibernate发行包中附带的第三方类库的列表,他们总是保持最新的。你可以在那里找到所有必需或者可选的类库(注意:其中的"buildtime required"指的是编译Hibernate时所需要而非编译你自己的程序所必需的类库)。

接下来我们来配置在Tomcat和Hibernate中共用的数据库连接池。也就是说Tomcat会提供经过池处理的JDBC连接(用它内置的DBCP连接池),Hibernate通过JNDI方式来请求获得JDBC连接。作为替代方案,你也可以让Hibernate自行管理连接池。Tomcat把连接池绑定到JNDI,我们要在Tomcat的主配置文件(TOMCAT/conf/server.xml)中加一个资源声明:

<Context path="/quickstart" docBase="quickstart">
    <Resource name="jdbc/quickstart" scope="Shareable" type="javax.sql.DataSource"/>
    <ResourceParams name="jdbc/quickstart">
        <parameter>
            <name>factory</name>
            <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
        </parameter>

        <!-- DBCP database connection settings -->
        <parameter>
            <name>url</name>
            <value>jdbc:postgresql://localhost/quickstart</value>
        </parameter>
        <parameter>
            <name>driverClassName</name><value>org.postgresql.Driver</value>
        </parameter>
        <parameter>
            <name>username</name>
            <value>quickstart</value>
        </parameter>
        <parameter>
            <name>password</name>
            <value>secret</value>
        </parameter>

        <!-- DBCP connection pooling options -->
        <parameter>
            <name>maxWait</name>
            <value>3000</value>
        </parameter>
        <parameter>
            <name>maxIdle</name>
            <value>100</value>
        </parameter>
        <parameter>
            <name>maxActive</name>
            <value>10</value>
        </parameter>
    </ResourceParams>
</Context>

我们在这个例子中要配置的上下文叫做quickstart,它位于TOMCAT/webapp/quickstart目录下。如果要访问这个应用程序,在你的浏览器中输入http://localhost:8080/quickstart就可以了(当然,在后面加上在你的web.xml文件中配置好你的servlet)。你现在可以创建一个只含有空process()的简单servlet了。

Tomcat现在通过JNDI的方式:java:comp/env/jdbc/quickstart来提供连接。如果你在配置连接池遇到问题,请查阅Tomcat文档。如果你遇到了JDBC驱动所报的exception出错信息,请在没有Hibernate的环境下,先测试JDBC连接池本身是否配置正确。Tomcat和JDBC的配置教程可以在Web上查到。

下一步就是配置Hibernate。首先Hibernate必须知道它如何获得JDBC连接,在这里我们使用基于XML格式的Hibernate配置文件。当然使用properties文件的进行配置,但缺少一些XML语法的特性。这个XML配置文件必须放在上下文类路径(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-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <property name="connection.datasource">java:comp/env/jdbc/quickstart</property>
        <property name="show_sql">false</property>
        <property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>

        <!-- Mapping files -->
        <mapping resource="Cat.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

在这里我们关闭了SQL命令的log,同时告诉Hibernate使用哪种SQL数据库用语(Dialet),以及如何得到JDBC连接(通过Tomcat声明绑定的JNDI地址)。Dialet是必需配置的,因为不同的数据库都和"SQL标准"有一些出入。不用担心,Hibernate会替你处理这些差异,Hibernate支持所有主流的商业和开放源代码数据库。

SessionFactory是Hibernate的一个概念,表示对应一个数据存储源。通过创建多个XML配置文件并在你的程序中创建多个ConfigurationSessionFactory对象,就可以支持多个数据库了。

hibernate.cfg.xml中的最后一个元素声明了Cat.hbm.xml,这是一个Hibernate XML映射文件,对应于持久化类Cat。这个文件包含了把Cat POJO类映射到数据库表(或多个数据库表)的元数据。我们稍后就回来看这个文件。下一步让我们先编写这个POJO类,然后在声明它的映射元数据。

1.2. 第一个持久化类

Hibernate使用简单的Java对象(Plain Old Java Objects ,就是POJOs,有时候也称作Plain Ordinary Java Objects)这种编程模型来进行持久化。一个POJO很像JavaBean,通过getter和setter方法访问其属性,对外则隐藏了内部实现的细节(假若需要的话,Hibernate也可以直接访问其属性字段)。

package org.hibernate.examples.quickstart;

public class Cat {

    private String id;
    private String name;
    private char sex;
    private float weight;

    public Cat() {
    }

    public String getId() {
        return id;
    }

    private void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public float getWeight() {
        return weight;
    }

    public void setWeight(float weight) {
        this.weight = weight;
    }

}

Hibernate对属性使用的类型不加任何限制。所有的Java JDK类型和原始类型(比如String,charDate)都可以被映射,也包括Java 集合(Java collections framework)中的类。你可以把它们映射成为值,值集合,或者与其他实体类相关联。id是一个特殊的属性,代表了这个类的数据库标识符(主键),对于类似于Cat这样的实体类我们强烈建议使用。Hibernate也可以使用内部标识符,但这样我们会失去一些程序架构方面的灵活性。

持久化类不需要实现什么特别的接口,也不需要从一个特别的持久化根类继承下来。Hibernate也不需要使用任何编译期处理,比如字节码增强操作,它独立的使用Java反射机制和运行时类增强(通过CGLIB)。所以不依赖于Hibernate,我们就可以把POJO的类映射成为数据库表。

1.3. 映射cat

Cat.hbm.xml映射文件包含了对象/关系映射(O/R Mapping)所需的元数据。元数据包含持久化类的声明和属性到数据库的映射(指向字段和其他实体的外键关联)。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
    PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

    <class name="org.hibernate.examples.quickstart.Cat" table="CAT">

        <!-- A 32 hex character is our surrogate key. It's automatically
            generated by Hibernate with the UUID pattern. -->
        <id name="id" type="string" unsaved-value="null" >
            <column name="CAT_ID" sql-type="char(32)" not-null="true"/>
            <generator class="uuid.hex"/>
        </id>

        <!-- A cat has to have a name, but it shouldn' be too long. -->
        <property name="name">
            <column name="NAME" length="16" not-null="true"/>
        </property>

        <property name="sex"/>

        <property name="weight"/>

    </class>

</hibernate-mapping>

每个持久化类都应该有一个标识属性(实际上,这个类只代表实体,而不是独立的值类型类,后者会被映射称为实体对象中的一个组件)。这个属性用来区分持久化对象:如果catA.getId().equals(catB.getId())结果是true的话,这两个Cat就是相同的。这个概念称为数据库标识。Hiernate附带了几种不同的标识符生成器,用于不同的场合(包括数据库本地的顺序(sequence)生成器、hi/lo高低位标识模式、和程序自己定义的标识符)。我们在这里使用UUID生成器(只在测试时建议使用,如果使用数据库自己生成的整数类型的键值更好),并指定CAT表中的CAT_ID字段(作为表的主键)存放生成的标识值。

Cat的其他属性都映射到同一个表的字段。对name属性来说,我们把它显式地声明映射到一个数据库字段。如果数据库schema是通过由映射声明使用Hibernate的SchemaExport工具自动生成的(作为SQL DDL指令)的话,这就特别有用。所有其它的属性都用Hibernate的默认值映射,大多数情况你都会这样做。数据库中的CAT表看起来是这样的:

 Column |         Type          | Modifiers
--------+-----------------------+-----------
 cat_id | character(32)         | not null
 name   | character varying(16) | not null
 sex    | character(1)          |
 weight | real                  |
Indexes: cat_pkey primary key btree (cat_id)

你现在可以在你的数据库中手工创建这个表了,如果你需要使用hbm2ddl工具把这个步骤自动化,请参阅第 21 章 工具箱指南。这个工具能够创建完整的SQL DDL,包括表定义,自定义的字段类型约束,惟一约束和索引。

1.4. 与Cat同乐

我们现在可以开始Hibernate的Session了。它是一个持久化管理器,我们通过它来从数据库中存取Cat。首先,我们要从SessionFactory中获取一个Session(Hibernate的工作单元)。

SessionFactory sessionFactory =
            new Configuration().configure().buildSessionFactory();

通过对configure()的调用来装载hibernate.cfg.xml配置文件,并初始化成一个Configuration实例。 在创建 SessionFactory之前(它是不可变的),你可以访问Configuration来设置其他属性(甚至修改映射的元数据)。我们应该在哪儿创建SessionFactory,在我们的程序中又如何访问它呢? SessionFactory通常只是被初始化一次,比如说通过一个load-on-startup servlet的来初始化。这意味着你不应该在serlvet中把它作为一个实例变量来持有,而应该放在其他地方。进一步的说,我们需要使用单例(Singleton)模式,我们才能更容易的在程序中访问SessionFactory。下面的方法就同时解决了两个问题:对SessionFactory的初始配置与便捷使用。

我们实现一个HibernateUtil辅助类:

import org.hibernate.*;
import org.hibernate.cfg.*;

public class HibernateUtil {

    private static Log log = LogFactory.getLog(HibernateUtil.class);

    private static final SessionFactory sessionFactory;

    static {
        try {
            // Create the SessionFactory
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            log.error("Initial SessionFactory creation failed.", ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static final ThreadLocal session = new ThreadLocal();

    public static Session currentSession()  {
        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() {
        Session s = (Session) session.get();
        if (s != null)
            s.close();
        session.set(null);
    }
}

这个类不但在它的静态初始器中使用了SessionFactory,还使用了一个ThreadLocal变量来保存Session做为当前工作线程。在你使用这个辅助类之前,请确保你理解了thread-local变量这个Java概念。你可以在CaveatEmptor(http://caveatemptor.hibernate.org/)上找到一个更加复杂和强大的 HibernateUtil

SessionFactory是安全线程,可以由很多线程并发访问并获取到Sessions。单个Session不是安全线程对象,它只代表与数据库之间的一次操作。Session通过SessionFactory获得并在所有的工作完成后关闭。在你servlet的process()中可以象是这么写的(省略了异常情况处理):

Session session = HibernateUtil.currentSession();

Transaction tx= session.beginTransaction();

Cat princess = new Cat();
princess.setName("Princess");
princess.setSex('F');
princess.setWeight(7.4f);

session.save(princess);
tx.commit();

HibernateUtil.closeSession();

在一个Session中,每个数据库操作都是在一个事务(transaction)中进行的,这样就可以隔离开不同的操作(甚至包括只读操作)。我们使用Hibernate的Transaction API来从底层的事务策略中(本例中是JDBC事务)脱身出来。这样,我们就不需要更改任何源代码,就可以把我们的程序部署到一个由容器管理事务的环境中去(使用JTA)。

这样你就可以随心所欲的多次调用HibernateUtil.currentSession();,你每次都会得到同一个当前线程的Session。不管是在你的servlet代码中,或者在servlet filter中还是在HTTP结果返回之前,你都必须确保这个Session在你的数据库访问工作完成后关闭。这样做还有一个好处就是可以容易的使用延迟装载(lazy initialization):Session在渲染view层的时候仍然打开着的,所以你在遍历当前对象图的时候可以装载所需的对象。

Hibernate有不同的方法用来从数据库中取回对象。最灵活的方式就是使用Hibernate查询语言(HQL),这是一种容易学习的语言,是对SQL的面向对象的强大扩展。

Transaction tx= session.beginTransaction();

Query query = session.createQuery("select c from Cat as c where c.sex = :sex");
query.setCharacter("sex", 'F');
for (Iterator it = query.iterate(); it.hasNext();) {
    Cat cat = (Cat) it.next();
    out.println("Female Cat: " + cat.getName() );
}

tx.commit();

Hibernate也提供一种面向对象的按条件查询API,可以执行简洁安全类型的查询。当然,Hibernate在所有与数据库的交互中都使用PrepatedStatement和参数绑定。你也可以使用Hibernate的直接SQL查询特性,或者在特殊情况下从Session获取一个原始的JDBC连接。

1.5. 结语

在这个短小的教程中,我们对Hibernate浅尝即止。请注意我们没有在例子中包含任何servlet相关代码。你必须自行编写servlet,并插入适合你的Hibernate代码。

请记住Hibernate作为一个数据库访问层,是与你的程序紧密相关的。通常情况下,所有其他层次都依赖持久机制。请确信你理解了这种设计的内涵。

若希望学习更复杂的例子,请参阅http://caveatemptor.hibernate.org/ 。在 http://www.hibernate.org/Documentation 也可以得到其他教程的链接。

第 2 章  Hibernate入门

2.1.  前言

本章是面向Hibernate初学者的一个介绍教程。我们将使用容易理解的方式,开发一个使用驻留内存式(in-memory)数据库的简单命令行程序。

本教程是面向Hibernate初学者,但是需要一定的Java和SQL知识。 它在Michael Goegl所写的一个教程的基础上完成的。我们使用的第三方库文件是支持JDK 1.4和5.0。如果你要使用JDK1.3,可能会需要其它的库。

2.2.  第一部分 - 第一个Hibernate程序

首先我们将创建一个简单的控制台(console-based)Hibernate程序。我们使用内置数据库(in-memory database) (HSQL DB),所以我们不必安装任何数据库服务器。

让我们假设我们希望有一个小程序可以保存我们希望关注的事件(Event)和这些事件的信息。 (译者注:在本教程的后面部分,我们将直接使用Event而不是它的中文翻译“事件”,以免混淆。)

我们做的第一件事是建立我们的开发目录,并把所有需要用到的Java库文件放进去。 从Hibernate网站的下载页面下载Hibernate分发版本。 解压缩包并把/lib下面的所有库文件放到我们新的开发目录下面的/lib目录下面。 看起来就像这样:

.
+lib
  antlr.jar
  cglib-full.jar
  asm.jar
  asm-attrs.jars
  commons-collections.jar
  commons-logging.jar
  ehcache.jar
  hibernate3.jar
  jta.jar
  dom4j.jar
  log4j.jar 

This is the minimum set of required libraries (note that we also copied hibernate3.jar, the main archive) for Hibernate. See the README.txt file in the lib/ directory of the Hibernate distribution for more information about required and optional third-party libraries. (Actually, Log4j is not required but preferred by many developers.) 这个是Hibernate运行所需要的最小库文件集合(注意我们也拷贝了Hibernate3.jar,这个是最重要的库)。 可以在Hibernate分发版本的lib/目录下查看README.txt,以获取更多关于所需和可选的第三方库文件信息 (事实上,Log4j并不是必须的库文件但是许多开发者都喜欢用它)。

接下来我们创建一个类,用来代表那些我们希望储存在数据库里面的event.

2.2.1.  第一个class

我们的第一个持久化类是 一个简单的JavaBean class,带有一些简单的属性(property)。 让我们来看一下代码:

import java.util.Date;

public class Event {
    private Long id;

    private String title;
    private Date date;

    Event() {}

    public Long getId() {
        return id;
    }

    private void setId(Long id) {
        this.id = id;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

你可以看到这个class对属性(property)的存取方法(getter and setter method) 使用标准的JavaBean命名约定,同时把内部字段(field)隐藏起来(private visibility)。 这个是个受推荐的设计方式,但并不是必须这样做。 Hibernate也可以直接访问这些字段(field),而使用访问方法(accessor method)的好处是提供了程序重构的时候健壮性(robustness)。

id 属性(property) 为一个Event实例提供标识属性(identifier property)的值- 如果我们希望使用Hibernate的所有特性,那么我们所有的持久性实体类(persistent entity class)(这里也包括一些次要依赖类) 都需要一个标识属性(identifier property)。而事实上,大多数应用程序(特别是web应用程序)都需要识别特定的对象,所以你应该 考虑使用标识属性而不是把它当作一种限制。然而,我们通常不会直接操作一个对象的标识符(identifier), 因此标识符的setter方法应该被声明为私有的(private)。这样当一个对象被保存的时候,只有Hibernate可以为它分配标识符。 你会发现Hibernate可以直接访问被声明为public,private和protected等不同级别访问控制的方法(accessor method)和字段(field)。 所以选择哪种方式来访问属性是完全取决于你,你可以使你的选择与你的程序设计相吻合。

所有的持久类(persistent classes)都要求有无参的构造器(no-argument constructor); 因为Hibernate必须要使用Java反射机制(Reflection)来实例化对象。构造器(constructor)的访问控制可以是私有的(private), 然而当生成运行时代理(runtime proxy)的时候将要求使用至少是package级别的访问控制,这样在没有字节码编入 (bytecode instrumentation)的情况下,从持久化类里获取数据会更有效率一些。

我们把这个Java源代码文件放到我们的开发目录下面一个叫做src的目录里。 这个目录现在应该看起来像这样:

.
+lib
  <Hibernate and third-party libraries>
+src
  Event.java

在下一步里,我们将把这个持久类(persisten class)的信息通知Hibernate

2.2.2.  映射文件

Hibernate需要知道怎样去加载(load)和存储(store)我们的持久化类的对象。这里正是Hibernate映射文件(mapping file)发挥作用的地方。 映射文件告诉Hibernate它应该访问数据库里面的哪个表(table)和应该使用表里面的哪些字段(column)。

一个映射文件的基本结构看起来像这样:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
[...]
</hibernate-mapping>

注意Hibernate的DTD是非常复杂的。 你可以在你的编辑器或者IDE里面使用它来自动提示并完成(auto-completion)那些用来映射的XML元素(element)和属性(attribute)。 你也可以用你的文本编辑器打开DTD-这是最简单的方式来浏览所有元素和参数,查看它们的缺省值以及它们的注释,以得到一个整体的概观。 同时也要注意Hibernate不会从web上面获取DTD文件,虽然XML里面的URL也许会建议它这样做,但是Hibernate会首先查看你的程序的classpath。 DTD文件被包括在hibernate3.jar,同时也在Hibernate分发版的src/路径下。

在以后的例子里面,我们将通过省略DTD的声明来缩短代码长度。但是显然,在实际的程序中,DTD声明是必须的。

在两个hibernate-mapping标签(tag)中间, 我们包含了一个 class元素(element)。所有的持久性实体类(persistent entity classes)(再次声明, 这里也包括那些依赖类,就是那些次要的实体)都需要一个这样的映射,来映射到我们的SQL database。

<hibernate-mapping>

    <class name="Event" table="EVENTS">

    </class>

</hibernate-mapping>

我们到现在为止做的一切是告诉Hibernate怎样从数据库表(table)EVENTS里持久化和 加载Event类的对象,每个实例对应数据库里面的一行。现在我们将继续讨论有关唯一标识属性(unique identifier property)的映射。 另外,我们不希望去考虑怎样产生这个标识属性,我们将配置Hibernate的标识符生成策略(identifier generation strategy)来产生代用主键。

<hibernate-mapping>

    <class name="Event" table="EVENTS">
        <id name="id" column="EVENT_ID">
            <generator class="increment"/>
        </id>
    </class>

</hibernate-mapping>

id元素是标识属性(identifer property)的声明, name="id" 声明了Java属性(property)的名字 - Hibernate将使用getId()setId()来访问它。 字段参数(column attribute)则告诉Hibernate我们使用EVENTS表的哪个字段作为主键。 嵌套的generator元素指定了标识符的生成策略 - 在这里我们使用increment,这个是非常简单的在内存中直接生成数字的方法,多数用于测试(或教程)中。 Hibernate同时也支持使用数据库生成(database generated),全局唯一性(globally unique)和应用程序指定(application assigned) (或者你自己为任何已有策略所写的扩展) 这些方式来生成标识符。

最后我们还必须在映射文件里面包括需要持久化属性的声明。缺省的情况下,类里面的属性都被视为非持久化的:

<hibernate-mapping>

    <class name="Event" table="EVENTS">
        <id name="id" column="EVENT_ID">
            <generator class="increment"/>
        </id>
        <property name="date" type="timestamp" column="EVENT_DATE"/>
        <property name="title"/>
    </class>

</hibernate-mapping>

id元素类似,property元素的name参数 告诉Hibernate使用哪个getter和setter方法。

为什么date属性的映射包括column参数,但是title却没有? 当没有设定column参数的时候,Hibernate缺省使用属性名作为字段(column)名。对于title,这样工作得很好。 然而,date在多数的数据库里,是一个保留关键字,所以我们最好把它映射成另外一个名字。

下一件有趣的事情是title属性缺少一个type参数。 我们声明并使用在映射文件里面的type,并不像我们假想的那样,是Java data type, 同时也不是SQL database type。这些类型被称作Hibernate mapping types, 它们把数据类型从Java转换到SQL data types。如果映射的参数没有设置的话,Hibernate也将尝试去确定正确的类型转换和它的映射类型。 在某些情况下这个自动检测(在Java class上使用反射机制)不会产生你所期待或者 需要的缺省值。这里有个例子是关于date属性。Hibernate无法知道这个属性应该被映射成下面这些类型中的哪一个: SQL datetimestamptime。 我们通过声明属性映射timestamp来表示我们希望保存所有的关于日期和时间的信息。

这个映射文件(mapping file)应该被保存为Event.hbm.xml,和我们的EventJava 源文件放在同一个目录下。映射文件的名字可以是任意的,然而hbm.xml已经成为Hibernate开发者社区的习惯性约定。 现在目录应该看起来像这样:

.
+lib
  <Hibernate and third-party libraries>
+src
  Event.java
  Event.hbm.xml

我们继续进行Hibernate的主要配置。

2.2.3.  Hibernate配置

我们现在已经有了一个持久化类和它的映射文件,是时候配置Hibernate了。在我们做这个之前,我们需要一个数据库。 HSQL DB,一个java-based内嵌式SQL数据库(in-memory SQL Database),可以从HSQL DB的网站上下载。 实际上,你仅仅需要下载/lib/目录中的hsqldb.jar。把这个文件放在开发文件夹的lib/目录里面。

在开发目录下面创建一个叫做data的目录 - 这个是HSQL DB存储它的数据文件的地方。

Hibernate是你的程序里连接数据库的那个应用层,所以它需要连接用的信息。连接(connection)是通过一个也由我们配置的JDBC连接池(connection pool)。 Hibernate的分发版里面包括了一些open source的连接池,但是我们已经决定在这个教程里面使用内嵌式连接池。 如果你希望使用一个产品级的第三方连接池软件,你必须拷贝所需的库文件去你的classpath并使用不同的连接池设置。

为了配置Hibernate,我们可以使用一个简单的hibernate.properties文件, 或者一个稍微复杂的hibernate.cfg.xml,甚至可以完全使用程序来配置Hibernate。 多数用户喜欢使用XML配置文件:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
        <property name="connection.url">jdbc:hsqldb:data/tutorial</property>
        <property name="connection.username">sa</property>
        <property name="connection.password"></property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.HSQLDialect</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

        <!-- Drop and re-create the database schema on startup -->
        <property name="hbm2ddl.auto">create</property>

        <mapping resource="Event.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

注意这个XML配置使用了一个不同的DTD。我们配置Hibernate的SessionFactory- 一个关联于特定数据库全局性的工厂(factory)。如果你要使用多个数据库,通常应该在多个配置文件中使用多个<session-factory> 进行配置(在更早的启动步骤中进行)。

最开始的4个property元素包含必要的JDBC连接信息。dialectproperty 表明Hibernate应该产生针对特定数据库语法的SQL语句。hbm2ddl.auto选项将自动生成数据库表定义(schema)- 直接插入数据库中。当然这个选项也可以被关闭(通过去除这个选项)或者通过Ant任务SchemaExport来把数据库表定义导入一个文件中进行优化。 最后,为持久化类加入映射文件。

把这个文件拷贝到源代码目录下面,这样它就位于classpath的root路径上。Hibernate在启动时会自动 在它的根目录开始寻找名为hibernate.cfg.xml的配置文件。

2.2.4.  用Ant编译

在这个教程里面,我们将用Ant来编译程序。你必须先安装Ant-可以从Ant download page 下载它。怎样安装Ant不是这个教程的内容,请参考Ant manual。 当你安装完了Ant,我们就可以开始创建编译脚本,它的文件名是build.xml,把它直接放在开发目录下面。

完善Ant

注意Ant的分发版通常功能都是不完整的(就像Ant FAQ里面说得那样),所以你常常不得不需要自己动手来完善Ant。 例如:如果你希望在你的build文件里面使用JUnit功能。为了让JUnit任务被激活(这个教程里面我们并不需要这个任务), 你必须拷贝junit.jar到ANT_HOME/lib目录下或者删除ANT_HOME/lib/ant-junit.jar这个插件。

一个基本的build文件看起来像这样

<project name="hibernate-tutorial" default="compile">

    <property name="sourcedir" value="${basedir}/src"/>
    <property name="targetdir" value="${basedir}/bin"/>
    <property name="librarydir" value="${basedir}/lib"/>

    <path id="libraries">
        <fileset dir="${librarydir}">
            <include name="*.jar"/>
        </fileset>
    </path>

    <target name="clean">
        <delete dir="${targetdir}"/>
        <mkdir dir="${targetdir}"/>
    </target>

    <target name="compile" depends="clean, copy-resources">
      <javac srcdir="${sourcedir}"
             destdir="${targetdir}"
             classpathref="libraries"/>
    </target>

    <target name="copy-resources">
        <copy todir="${targetdir}">
            <fileset dir="${sourcedir}">
                <exclude name="**/*.java"/>
            </fileset>
        </copy>
    </target>

</project>

这个将告诉Ant把所有在lib目录下以.jar结尾的文件加入classpath中用来进行编译。 它也将把所有的非Java源代码文件,例如配置和Hibernate映射文件,拷贝到目标目录下。如果你现在运行Ant, 你将得到以下输出:

C:/hibernateTutorial/>ant
Buildfile: build.xml

copy-resources:
     [copy] Copying 2 files to C:/hibernateTutorial/bin

compile:
    [javac] Compiling 1 source file to C:/hibernateTutorial/bin

BUILD SUCCESSFUL
Total time: 1 second 

2.2.5.  安装和帮助

是时候来加载和储存一些Event对象了,但是首先我们不得不完成一些基础的代码。 我们必须启动Hibernate。这个启动过程包括创建一个全局性的SessoinFactory并把它储存在一个应用程序容易访问的地方。 SessionFactory可以创建并打开新的Session。 一个Session代表一个单线程的单元操作,SessionFactory则是一个线程安全的全局对象,只需要创建一次。

我们将创建一个HibernateUtil帮助类(helper class)来负责启动Hibernate并使 操作Session变得容易。这个帮助类将使用被称为ThreadLocal Session 的模式来保证当前的单元操作和当前线程相关联。让我们来看一眼它的实现:

import org.hibernate.*;
import org.hibernate.cfg.*;

public class HibernateUtil {

    public static final SessionFactory sessionFactory;

    static {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(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();
            // Store it in the ThreadLocal variable
            session.set(s);
        }
        return s;
    }

    public static void closeSession() throws HibernateException {
        Session s = (Session) session.get();
        if (s != null)
            s.close();
        session.set(null);
    }
}

这个类不仅仅在它的静态初始化过程(仅当加载这个类的时候被JVM执行一次)中产生全局SessionFactory, 同时也有一个ThreadLocal变量来为当前线程保存Session。不论你何时 调用HibernateUtil.currentSession(),它总是返回同一个线程中的同一个Hibernate单元操作。 而一个HibernateUtil.closeSession()调用将终止当前线程相联系的那个单元操作。

在你使用这个帮助类之前,确定你明白Java关于本地线程变量(thread-local variable)的概念。一个功能更加强大的 HibernateUtil帮助类可以在CaveatEmptorhttp://caveatemptor.hibernate.org/找到 -它同时也出现在书:《Hibernate in Action》中。注意当你把Hibernate部署在一个J2EE应用服务器上的时候,这个类不是必须的: 一个Session会自动绑定到当前的JTA事物上,你可以通过JNDI来查找SessionFactory。 如果你使用JBoss AS,Hibernate可以被部署成一个受管理的系统服务(system service)并自动绑定SessionFactory到JNDI上。

HibernateUtil.java放在开发目录的源代码路径下面,与 Event.java放在一起:

.
+lib
  <Hibernate and third-party libraries>
+src
  Event.java
  Event.hbm.xml
  HibernateUtil.java
  hibernate.cfg.xml
+data
build.xml

再次编译这个程序不应该有问题。最后我们需要配置一个日志系统 - Hibernate使用通用日志接口,这允许你在Log4j和 JDK 1.4 logging之间进行选择。多数开发者喜欢Log4j:从Hibernate的分发版(它在etc/目录下)拷贝 log4j.properties到你的src目录,与hibernate.cfg.xml.放在一起。 看一眼配置示例,你可以修改配置如果你希望看到更多的输出信息。缺省情况下,只有Hibernate的启动信息会显示在标准输出上。

教程的基本框架完成了 - 现在我们可以用Hibernate来做些真正的工作。

2.2.6.  加载并存储对象

终于,我们可以使用Hibernate来加载和存储对象了。我们编写一个带有main()方法 的EventManager类:

import org.hibernate.Transaction;
import org.hibernate.Session;

import java.util.Date;

public class EventManager {

    public static void main(String[] args) {
        EventManager mgr = new EventManager();

        if (args[0].equals("store")) {
            mgr.createAndStoreEvent("My Event", new Date());
        }

        HibernateUtil.sessionFactory.close();
    }

}

我们从命令行读入一些参数,如果第一个参数是"store",我们创建并储存一个新的Event:

private void createAndStoreEvent(String title, Date theDate) {
    Session session = HibernateUtil.currentSession();
    Transaction tx = session.beginTransaction();

    Event theEvent = new Event();
    theEvent.setTitle(title);
    theEvent.setDate(theDate);

    session.save(theEvent);

    tx.commit();
    HibernateUtil.closeSession();
}

我们创建一个新的Event对象并把它传递给Hibernate。Hibernate现在负责创建SQL并把 INSERT命令传给数据库。在运行它之前,让我们花一点时间在SessionTransaction的处理代码上。

每个Session是一个独立的单元操作。你会对我们有另外一个API:Transaction而感到惊奇。 这暗示一个单元操作可以拥有比一个单独的数据库事务更长的生命周期 - 想像在web应用程序中,一个单元操作跨越多个Http request/response循环 (例如一个创建对话框)。根据“应用程序用户眼中的单元操作”来切割事务是Hibernate的基本设计思想之一。我们调用 一个长生命期的单元操作Application Transaction时,通常包装几个更生命期较短的数据库事务。 为了简化问题,在这个教程里我们使用SessionTransaction之间是1对1关系的粒度(one-to-one granularity)。

Transaction.begin()commit()都做些什么?rollback()在哪些情况下会产生错误? Hibernate的Transaction API 实际上是可选的, 但是我们通常会为了便利性和可移植性而使用它。 如果你宁可自己处理数据库事务(例如,调用session.connection.commit()),通过直接和无管理的JDBC,这样将把代码绑定到一个特定的部署环境中去。 通过在Hibernate配置中设置Transaction工厂,你可以把你的持久化层部署在任何地方。 查看第 12 章 事务和并发了解更多关于事务处理和划分的信息。在这个例子中我们也忽略任何异常处理和事务回滚。

为了第一次运行我们的应用程序,我们必须增加一个可以调用的target到Ant的build文件中。

<target name="run" depends="compile">
     <java fork="true" classname="EventManager" classpathref="libraries">
        <classpath path="${targetdir}"/>
        <arg value="${action}"/>
    </java>
</target>

action参数的值是在通过命令行调用这个target的时候设置的:

C:/hibernateTutorial/>ant run -Daction=store

你应该会看到,编译结束以后,Hibernate根据你的配置启动,并产生一大堆的输出日志。在日志最后你会看到下面这行:

[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)

这是Hibernate执行的INSERT命令,问号代表JDBC的待绑定参数。如果想要看到绑定参数的值或者减少日志的长度, 检查你在log4j.properties文件里的设置。

现在我们想要列出所有已经被存储的event,所以我们增加一个条件分支选项到main方法中去。

if (args[0].equals("store")) {
    mgr.createAndStoreEvent("My Event", new Date());
}
else if (args[0].equals("list")) {
    List events = mgr.listEvents();
    for (int i = 0; i < events.size(); i++) {
        Event theEvent = (Event) events.get(i);
        System.out.println("Event: " + theEvent.getTitle() +
                           " Time: " + theEvent.getDate());
    }
}

我们也增加一个新的listEvents()方法:

private List listEvents() {
    Session session = HibernateUtil.currentSession();
    Transaction tx = session.beginTransaction();

    List result = session.createQuery("from Event").list();

    tx.commit();
    session.close();

    return result;
}

我们在这里是用一个HQL(Hibernate Query Language-Hibernate查询语言)查询语句来从数据库中 加载所有存在的Event。Hibernate会生成正确的SQL,发送到数据库并使用查询到的数据来生成Event对象。 当然你也可以使用HQL来创建更加复杂的查询。

如果你现在使用命令行参数-Daction=list来运行Ant,你会看到那些至今为止我们储存的Event。 如果你是一直一步步的跟随这个教程进行的,你也许会吃惊这个并不能工作 - 结果永远为空。原因是hbm2ddl.auto 打开了一个Hibernate的配置选项:这使得Hibernate会在每次运行的时候重新创建数据库。通过从配置里删除这个选项来禁止它。 运行了几次store之后,再运行list,你会看到结果出现在列表里。 另外,自动生成数据库表并导出在单元测试中是非常有用的。

2.3.  第二部分 - 关联映射

我们已经映射了一个持久化实体类到一个表上。让我们在这个基础上增加一些类之间的关联性。 首先我们往我们程序里面增加人(people)的概念,并存储他们所参与的一个Event列表。 (译者注:与Event一样,我们在后面的教程中将直接使用person来表示“人”而不是它的中文翻译)

2.3.1.  映射Person类

最初的Person类是简单的:

public class Person {

    private Long id;
    private int age;
    private String firstname;
    private String lastname;

    Person() {}

    // Accessor methods for all properties, private setter for 'id'

}

Create a new mapping file called Person.hbm.xml:

<hibernate-mapping>

    <class name="Person" table="PERSON">
        <id name="id" column="PERSON_ID">
            <generator class="increment"/>
        </id>
        <property name="age"/>
        <property name="firstname"/>
        <property name="lastname"/>
    </class>

</hibernate-mapping>

Finally, add the new mapping to Hibernate's configuration:

        <mapping resource="Event.hbm.xml"/>
        <mapping resource="Person.hbm.xml"/>

我们现在将在这两个实体类之间创建一个关联。显然,person可以参与一系列Event,而Event也有不同的参加者(person)。 设计上面我们需要考虑的问题是关联的方向(directionality),阶数(multiplicity)和集合(collection)的行为。

2.3.2.  一个单向的Set-based关联

我们将向Person类增加一组Event。这样我们可以轻松的通过调用aPerson.getEvents() 得到一个Person所参与的Event列表,而不必执行一个显式的查询。我们使用一个Java的集合类:一个Set,因为Set 不允许包括重复的元素而且排序和我们无关。

目前为止我们设计了一个单向的,在一端有许多值与之对应的关联,通过Set来实现。 让我们为这个在Java类里编码并映射这个关联:

public class Person {

    private Set events = new HashSet();

    public Set getEvents() {
        return events;
    }

    public void setEvents(Set events) {
        this.events = events;
    }
}

在我们映射这个关联之前,先考虑这个关联另外一端。很显然的,我们可以保持这个关联是单向的。如果我们希望这个关联是双向的, 我们可以在Event里创建另外一个集合,例如:anEvent.getParticipants()。 这是留给你的一个设计选项,但是从这个讨论中我们可以很清楚的了解什么是关联的阶数(multiplicity):在这个关联的两端都是“多”。 我们叫这个为:多对多(many-to-many)关联。因此,我们使用Hibernate的many-to-many映射:

<class name="Person" table="PERSON">
    <id name="id" column="PERSON_ID">
        <generator class="increment"/>
    </id>
    <property name="age"/>
    <property name="firstname"/>
    <property name="lastname"/>

    <set name="events" table="PERSON_EVENT">
        <key column="PERSON_ID"/>
        <many-to-many column="EVENT_ID" class="Event"/>
    </set>

</class>

Hibernate支持所有种类的集合映射,<set>是最普遍被使用的。对于多对多(many-to-many)关联(或者叫n:m实体关系), 需要一个用来储存关联的表(association table)。里面的每一行代表从一个person到一个event的一个关联。 表名是由set元素的table属性值配置的。关联里面的标识字段名,person的一端,是 由<key>元素定义,event一端的字段名是由<many-to-many>元素的 column属性定义的。你也必须告诉Hibernate集合中对象的类(也就是位于这个集合所代表的关联另外一端的类)。

这个映射的数据库表定义如下:

    _____________        __________________
   |             |      |                  |       _____________
   |   EVENTS    |      |   PERSON_EVENT   |      |             |
   |_____________|      |__________________|      |    PERSON   |
   |             |      |                  |      |_____________|
   | *EVENT_ID   | <--> | *EVENT_ID        |      |             |
   |  EVENT_DATE |      | *PERSON_ID       | <--> | *PERSON_ID  |
   |  TITLE      |      |__________________|      |  AGE        |
   |_____________|                                |  FIRSTNAME  |
                                                  |  LASTNAME   |
                                                  |_____________|
 

2.3.3.  使关联工作

让我们把一些people和event放到EventManager的一个新方法中:

private void addPersonToEvent(Long personId, Long eventId) {
    Session session = HibernateUtil.currentSession();
    Transaction tx = session.beginTransaction();

    Person aPerson = (Person) session.load(Person.class, personId);
    Event anEvent = (Event) session.load(Event.class, eventId);

    aPerson.getEvents().add(anEvent);

    tx.commit();
    HibernateUtil.closeSession();
}

在加载一个Person和一个Event之后,简单的使用普通的方法修改集合。 如你所见,没有显式的update()或者save(), Hibernate自动检测到集合已经被修改 并需要保存。这个叫做automatic dirty checking,你也可以尝试修改任何对象的name或者date的参数。 只要他们处于persistent状态,也就是被绑定在某个Hibernate Session上(例如:他们 刚刚在一个单元操作从被加载或者保存),Hibernate监视任何改变并在后台隐式执行SQL。同步内存状态和数据库的过程,通常只在 一个单元操作结束的时候发生,这个过程被叫做flushing

你当然也可以在不同的单元操作里面加载person和event。或者在一个Session以外修改一个 不是处在持久化(persistent)状态下的对象(如果该对象以前曾经被持久化,我们称这个状态为脱管(detached))。 在程序里,看起来像下面这样:

    private void addPersonToEvent(Long personId, Long eventId) {

        Session session = HibernateUtil.currentSession();
        Transaction tx = session.beginTransaction();

        Person aPerson = (Person) session.load(Person.class, personId);
        Event anEvent = (Event) session.load(Event.class, eventId);

        tx.commit();
        HibernateUtil.closeSession();

        aPerson.getEvents().add(anEvent); // aPerson is detached

        Session session2 = HibernateUtil.currentSession();
        Transaction tx2 = session.beginTransaction();

        session2.update(aPerson); // Reattachment of aPerson

        tx2.commit();
        HibernateUtil.closeSession();
    }

update的调用使一个脱管对象(detached object)重新持久化,你可以说它被绑定到 一个新的单元操作上,所以任何你对它在脱管(detached)状态下所做的修改都会被保存到数据库里。

这个对我们当前的情形不是很有用,但是它是非常重要的概念,你可以把它设计进你自己的程序中。现在,加进一个新的 选项到EventManager的main方法中,并从命令行运行它来完成这个练习。如果你需要一个person和 一个event的标识符 - save()返回它。*******这最后一句看不明白

上面是一个关于两个同等地位的类间关联的例子,这是在两个实体之间。像前面所提到的那样,也存在其它的特别的类和类型,这些类和类型通常是“次要的”。 其中一些你已经看到过,好像int或者String。我们称呼这些类为值类型(value type), 它们的实例依赖(depend)在某个特定的实体上。这些类型的实例没有自己的身份(identity),也不能在实体间共享 (比如两个person不能引用同一个firstname对象,即使他们有相同的名字)。当然,value types并不仅仅在JDK中存在 (事实上,在一个Hibernate程序中,所有的JDK类都被视为值类型),你也可以写你自己的依赖类,例如AddressMonetaryAmount

你也可以设计一个值类型的集合(collection of value types),这个在概念上与实体的集合有很大的不同,但是在Java里面看起来几乎是一样的。

2.3.4.  值类型的集合

我们把一个值类型对象的集合加入Person。我们希望保存email地址,所以我们使用String, 而这次的集合类型又是Set

private Set emailAddresses = new HashSet();

public Set getEmailAddresses() {
    return emailAddresses;
}

public void setEmailAddresses(Set emailAddresses) {
    this.emailAddresses = emailAddresses;
}

Set的映射

<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
    <key column="PERSON_ID"/>
    <element type="string" column="EMAIL_ADDR"/>
</set>

比较这次和较早先的映射,差别主要在element部分这次并没有包括对其它实体类型的引用,而是使用一个元素类型是 String的集合(这里使用小写的名字是向你表明它是一个Hibernate的映射类型或者类型转换器)。 和以前一样,settable参数决定用于集合的数据库表名。key元素 定义了在集合表中使用的外键。element元素的column参数定义实际保存String值 的字段名。

看一下修改后的数据库表定义。

  _____________        __________________
 |             |      |                  |       _____________
 |   EVENTS    |      |   PERSON_EVENT   |      |             |       ___________________
 |_____________|      |__________________|      |    PERSON   |      |                   |
 |             |      |                  |      |_____________|      | PERSON_EMAIL_ADDR |
 | *EVENT_ID   | <--> | *EVENT_ID        |      |             |      |___________________|
 |  EVENT_DATE |      | *PERSON_ID       | <--> | *PERSON_ID  | <--> |  *PERSON_ID       |
 |  TITLE      |      |__________________|      |  AGE        |      |  *EMAIL_ADDR      |
 |_____________|                                |  FIRSTNAME  |      |___________________|
                                                |  LASTNAME   |
                                                |_____________|
 

你可以看到集合表(collection table)的主键实际上是个复合主键,同时使用了2个字段。这也暗示了对于同一个 person不能有重复的email地址,这正是Java里面使用Set时候所需要的语义(Set里元素不能重复)。

你现在可以试着把元素加入这个集合,就像我们在之前关联person和event的那样。Java里面的代码是相同的。

2.3.5.  双向关联

下面我们将映射一个双向关联(bi-directional association)- 在Java里面让person和event可以从关联的 任何一端访问另一端。当然,数据库表定义没有改变,我们仍然需要多对多(many-to-many)的阶数(multiplicity)。一个关系型数据库要比网络编程语言 更加灵活,所以它并不需要任何像导航方向(navigation direction)的东西 - 数据可以用任何可能的方式进行查看和获取。

首先,把一个参与者(person)的集合加入Event类中:

private Set participants = new HashSet();

public Set getParticipants() {
    return participants;
}

public void setParticipants(Set participants) {
    this.participants = participants;
}

Event.hbm.xml里面也映射这个关联。

<set name="participants" table="PERSON_EVENT" inverse="true">
    <key column="EVENT_ID"/>
    <many-to-many column="PERSON_ID" class="Person"/>
</set>

如你所见,2个映射文件里都有通常的set映射。注意keymany-to-many 里面的字段名在两个映射文件中是交换的。这里最重要的不同是Event映射文件里set元素的 inverse="true"参数。

这个表示Hibernate需要在两个实体间查找关联信息的时候,应该使用关联的另外一端 - Person类。 这将会极大的帮助你理解双向关联是如何在我们的两个实体间创建的。

2.3.6.  使双向关联工作

首先,请牢记在心,Hibernate并不影响通常的Java语义。 在单向关联中,我们是怎样在一个Person和一个Event之间创建联系的? 我们把一个Event的实例加到一个Person类内的Event集合里。所以,显然如果我们要让这个关联可以双向工作, 我们需要在另外一端做同样的事情 - 把Person加到一个Event类内的Person集合中。 这“在关联的两端设置联系”是绝对必要的而且你永远不应该忘记做它。

许多开发者通过创建管理关联的方法来保证正确的设置了关联的两端,比如在Person里:

protected Set getEvents() {
    return events;
}

protected void setEvents(Set events) {
    this.events = events;
}

public void addToEvent(Event event) {
    this.getEvents().add(event);
    event.getParticipants().add(this);
}

public void removeFromEvent(Event event) {
    this.getEvents().remove(event);
    event.getParticipants().remove(this);
}

注意现在对于集合的get和set方法的访问控制级别是protected - 这允许在位于同一个包(package)中的类以及继承自这个类的子类 可以访问这些方法,但是禁止其它的直接外部访问,避免了集合的内容出现混乱。你应该尽可能的在集合所对应的另外一端也这样做。

inverse映射参数究竟表示什么呢?对于你和对于Java来说,一个双向关联仅仅是在两端简单的设置引用。然而仅仅这样 Hibernate并没有足够的信息去正确的产生INSERTUPDATE语句(以避免违反数据库约束), 所以Hibernate需要一些帮助来正确的处理双向关联。把关联的一端设置为inverse将告诉Hibernate忽略关联的 这一端,把这端看成是另外一端的一个镜子(mirror)。这就是Hibernate所需的信息,Hibernate用它来处理如何把把 一个数据导航模型映射到关系数据库表定义。 你仅仅需要记住下面这个直观的规则:所有的双向关联需要有一端被设置为inverse。在一个一对多(one-to-many)关联中 它必须是代表多(many)的那端。而在多对多(many-to-many)关联中,你可以任意选取一端,两端之间并没有差别。

2.4.  总结

这个教程覆盖了关于开发一个简单的Hibernate应用程序的几个基础方面。

如果你已经对Hibernate感到自信,继续浏览开发指南里你感兴趣的内容-那些会被问到的问题大多是事务处理 (第 12 章 事务和并发), 抓取(fetch)的效率 (第 20 章 提升性能 ),或者API的使用 (第 11 章 与对象共事)和查询的特性(第 11.4 节 “查询”)。

不要忘记去Hibernate的网站查看更多(有针对性的)教程。

第 3 章 体系结构(Architecture)

3.1. 概况(Overview

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值