1.4 ORM
看过了对象持久化可供选择的方法之后,该介绍我们认为是最好的、Hibernate使用的一种解决方案ORM了。除了ORM的悠久历史之外(最早的研究论文发表于20世纪80年代末期),开发人员对ORM术语的使用也各不相同。有些人称它为“对象关系映射”,其他人则喜欢简单的“对象映射”;我们专门使用术语“对象/关系映射”和它的首字母(Object/Relational Mapping)缩写词ORM。正斜杠强调了当这两个领域整合时产生的不匹配问题。
本节首先看看什么是ORM。然后列举出一个好的ORM解决方案需要解决的问题。最后,讨论ORM提供的大致的好处,以及我们为什么推荐这种解决方案。
1.4.1 什么是ORM
简而言之,ORM就是利用描述对象和数据库之间映射的元数据,自动(且透明)地把Java应用程序中的对象持久化到关系数据库中的表。
ORM本质上是把数据从一种表示法(可逆)转换为另一种表示法进行工作。这意味着某些性能损失。然而,如果ORM作为中间件实现,就有许多手工编码的持久层所没有的优化机会。控制转换的元数据的规定和管理在开发时增加了企业日常开支,但是其成本却少于维护一个手工编码的解决方案所需的成本。(甚至对象数据库需要大量的元数据。)
常见问题 ORM难道不是一个Visio插件吗?首字母缩写的ORM也可以是对象角色建模(Object Role Modeling)的意思,且这个术语出现在相关的ORM之前。它描述了一种在数据库建模中使用的信息分析方法,主要由Microsoft Visio(一种图形化建模工具)支持。数据库专家们用它作为最普及的实体-关系建模(Entity-Relationship Modeling)的替代或者补充。然而,如果你和Java开发人员谈ORM,通常是在ORM的环境中。
ORM解决方案包含下面的4个部分:
l 在持久化类的对象上执行基本的CRUD操作的一个API;
l 用于指定引用类或者类属性的查询的一种语言或者API;
l 用于指定映射元数据的一种工具;
l 用于实现ORM的一项技术,与事务对象交互,执行脏检查(dirty checking)、延迟关联抓取以及其他优化功能。
我们使用完整的ORM术语,包括从一个基于元数据的描述中自动产生SQL的任何持久层。我们不包括开发人员用JDBC手工编写SQL来手动解决ORM问题的持久层。使用ORM,应用程序与ORM API和领域模型类交互,并从底层的SQL/JDBC中被抽象出来。依赖于这些特性或者特定的实现,ORM引擎也可能承担如乐观锁(optimistic locking)和高速缓存这类问题,完全免去了应用程序对这些问题的关注。
来看一下可以实现ORM的各种方法。Mark Fussel(Fussel,1997),ORM领域的一位开发人员,定义了下列4个ORM质量等级。我们稍微改写了他的描述,并把它们放在当今Java应用程序开发的上下文中。
1.纯关系
整个应用程序(包括用户界面)都围绕着关系模型和基于SQL的关系操作而设计。这种方法,除了它不足以用于大型系统之外,对于那些容许低级代码重用的简单应用程序来说,它不失为一种极好的解决方案。直接的SQL可以在各个方面进行调优,但是缺点(例如缺乏可移植性和可维护性)也是很显著的,尤其对长期运行而言。这类应用程序经常大量地使用存储过程,把一些工作从业务层转移到了数据库中。
2.轻量对象映射
实体被表示为手工映射到关系表的类。使用众所周知的设计模式,把手工编码的SQL/JDBC从业务逻辑中隐藏起来。这种方法非常普遍,对于那些带有少量实体的应用程序,或者那些使用普通的元数据驱动的数据模型的应用程序来说,它是很成功的。存储过程在这种类型的应用程序中可能也有一席之地。
3.中等对象映射
这种应用程序围绕对象模型而设计。SQL使用一个代码生成的工具在创建时产生,或者通过框架代码在运行时产生。对象之间的关联得到持久化机制的支持,并且查询可能使用一种面向对象的表达式语言来指定。对象由持久层高速缓存。ORM产品和自制的持久层都至少支持这一级别的功能。它非常适合一些复杂事务的中等规模的应用程序,特别是在不同的数据库产品之间的可移植性很重要的时候。这些应用程序通常不使用存储过程。
4.完全对象映射
完全的对象映射支持完善的对象模型:组合、继承、多态和可达的持久化。持久层实现了透明的持久化;持久化类不必继承任何特殊的基类,或者实现特殊的接口。高效的抓取策略(延迟、即时和预取)和高速缓存策略被透明地实现到应用程序。这一级别的功能无法通过自制的持久层实现——它相当于几年的开发时间。许多商业的和开源的Java ORM工具已经实现了这个质量等级。
这个等级符合本书中使用的ORM定义。来看一下希望通过实现完全对象映射的一个工具能够得以解决的一些问题。
1.4.2 一般的ORM问题
下列问题(称作ORM问题)列表标识了Java环境中被完全的ORM工具解决的一些根本问题。特别的ORM工具可能提供额外的功能(例如积极高速缓存),但这是个相当详尽的概念问题和特定于ORM问题的列表。
(1) 持久化类看起来什么样?持久化工具有多透明?我们必须对业务领域的类采用编程模型和惯例吗?
(2) 映射元数据如何定义?由于对象/关系转换完全由元数据控制,这个元数据的格式和定义很重要。ORM工具应该提供图形化用户界面(GUI)以便图形化地处理元数据吗?或者对于元数据的定义有没有更好的方法?
(3) 对象同一性和等同性如何与数据库(主键)同一性相关?如何映射特定类的实例到特定表的行?
(4) 应该如何映射类继承层次结构?有几种标准的策略。多态关联、抽象类和接口怎么样呢?
(5) 持久化逻辑如何在运行时与业务领域的对象交互?这是个一般的编程问题,并且有许多解决方案,包括源代码生成、运行时反射、运行时字节码生成和创建时字节码增强。这个问题的解决方案可能影响你的构建过程(但宁可如此,因为影响构建过程总好过影响用户)。
(6) 什么是持久化对象的生命周期?有些对象的生命周期取决于其他关联对象的生命周期吗?如何把一个对象的生命周期转变为一个数据库行的生命周期?
(7) 提供什么工具用来排序、搜索和统计?应用程序可以在内存中处理其中一些事情,但是为了有效地使用关系技术,经常需要由数据库来完成这项工作。
(8) 如何利用关联有效地获取数据?对关系型数据的有效访问通常经由表联结来完成。面向对象应用程序通常通过导航对象网络来访问数据。如果可能,这两种数据访问模式应该避免:n+1查询问题和它的补充笛卡儿积问题(在单个查询中抓取太多的数据)。
在一个ORM工具的设计和架构上强加基础约束的另外两个问题,对于任何数据访问技术都是共通的:
l 事务和并发性;
l 高速缓存管理(和并发性)。
如你所见,一个完全的ORM工具需要处理一个相当长的问题列表。现在为止,你应该开始体会到ORM的价值了。下一节介绍使用ORM解决方案所获得的一些其他益处。
1.4.3 为什么选择ORM
ORM的实现非常复杂——虽然没有应用程序服务器复杂,却比Web应用程序框架如Struts或者Tapestry要复杂得多。为什么要把另一个复杂的基础元素引入到我们的系统中呢?值得这么做吗?
给这些问题提供完整的答案,将要占用本书大部分的篇幅,但是本节只对最具竞争力的益处提供一个快速的概括。可是,首先要迅速处理一个非益处的问题。
ORM一个假定的益处是使开发人员避免杂乱的SQL。持这种观点的人认为不能期待面向对象的开发人员很好地理解SQL或者关系数据库,并且他们认为SQL有点讨厌。正好相反,我们认为Java开发人员必须足够熟悉并欣赏关系模型和SQL,以便用ORM进行工作。ORM是一项高级的技术,将被为其付出艰辛努力的开发人员所用。要有效地使用Hibernate,必须能够观察和解读它生成的SQL语句,并理解对于其性能的含义。
现在,来看看ORM和Hibernate的一些益处。
1.生产力
与持久化相关的代码可能会是Java应用程序中最冗长乏味的代码。Hibernate去除了许多琐碎的工作(比你想象的更多),并让你把精力集中在业务问题上。
无论你喜欢哪种应用程序开发策略——自顶向下,从一个领域模型开始;或者自底向上,从一个现有的数据库Schema开始——Hibernate与适当的工具一起使用,将明显减少开发时间。
2.可维护性
更少的代码行(LOC)使得系统更易于理解,因为它强调业务逻辑甚于那些费力的基础性工作。最重要的是,系统包含的代码越少则越易于重构。自动的对象/关系持久化充分地减少了LOC。当然,统计代码行是衡量应用程序复杂性的一种有争议的方式。
然而,Hibernate应用程序更易维护还有其他原因。在手工编码的持久化系统中,关系表示法和对象模型实现领域之间存在着一种必然的压力。改变一个,通常都要改变另一个,并且一个表示法设计经常需要妥协以便适应另一个的存在。(在实际应用程序中,通常是领域的对象模型发生妥协。)ORM提供了两个模型之间的一个缓冲,允许面向对象在Java方面进行更优雅的利用,并且每个模型的微小变化都不会传递到另一个模型。
3.性能
一种普遍的断言是,手工编码的持久化与自动的持久化相比总是至少可以一样快,并且经常更快。这是真的,就像汇编代码总是至少可以与Java代码一样快,或者手工编写的解析器总是至少可以与由YACC或者ANTLR产生的解析器一样快,这同样是真的一样——换句话说,这有点离题了。这种断言的言下之意是,手工编码的持久化在实际应用程序中将至少完成得一样好。但是,这种含意只有当实现至少一样快的手工编码的持久化所需的努力,类似于使用自动的解决方案所付出的努力时才是对的。真正值得关注的问题是,当我们考虑到时间和预算的约束时会发生什么?
给定一项持久化任务,有多种优化可能。有些(例如查询提示)用手工编码的SQL/JDBC更容易实现。然而,大部分优化用自动的ORM则更容易实现。在有时间限制的项目中,手工编码的持久化通常允许你进行一些优化。Hibernate始终允许使用更多的优化。此外,自动的持久化把开发人员的工作效率提高了那么多,使得开发人员能够花更多的时间对其他的少数瓶颈进行手工优化。
最后,实现你的ORM软件的人,可能比你更有时间研究性能优化问题。例如,你知道高速缓存PreparedStatement实例给DB2 JDBC驱动带来明显的性能提升,却破坏了InterBase JDBC驱动吗?你认识到只更新表中被改变的列对于有些数据库会明显变快,却潜在地减慢了其他的数据库吗?在你手工编写的解决方案中,试验这些不同策略之间的冲突容易吗?
4.供应商独立性
ORM从底层的SQL数据库和SQL方言中把应用程序抽象出来。如果这个工具支持许多不同的数据库(大部分都支持),那么这会给你的应用程序带来一定程度的可移植性。你不必期待一劳永逸(一次编写/到处运行),因为数据库的能力各异,实现完全的可移植性将需要牺牲这个更强大平台的一些优势。然而,用ORM通常更容易开发跨平台的应用程序。即使你不需要跨平台操作,ORM仍然可以帮助减小一些被供应商锁定的风险。
此外,数据库独立性在这种开发场景中也有帮助——开发人员在开发时使用轻量级的本地数据库,但实际的产品部署在不同的数据库上。
有时候,需要选择一种ORM产品。为了做出有根据的决定,需要一张可用的软件模块和标准的列表。
1.4.4 Hibernate、EJB 3和JPA简介
Hibernate是一个完全的ORM工具,提供前面列举的所有ORM的益处。在Hibernate中使用的API是原生的,并且是由Hibernate的开发人员设计的。对于查询接口和查询语言,以及ORM元数据如何定义,这也是一样的。
在用Hibernate开始第一个项目之前,应该考虑EJB 3.0标准和它的子规范Java Persistence。让我们回顾历史看看这个新标准是如何产生的。
许多Java开发人员认为EJB 2.1实体bean是持久层实现的技术之一。EJB编程和持久化模型在行业中已经被广泛采用,并且已经成了J2EE(或者现在称作Java EE)成功的一个重要因素。
然而,过去几年中,开发人员社区中的EJB批评家的声音却越来越大(特别有关实体bean和持久化),一些公司认识到应该改进EJB标准。Sun,作为J2EE的倡导者,知道修改势在必行,并启动了一个新的Java规范要求(JSR),目标是在2003年初简化EJB。这个新的JSR,EJB 3.0(JSR 220),引起了很大的关注。来自Hibernate团队的开发人员加入了早期的专家组,帮助制订出新规范的雏形。其他的供应商,包括Java行业中所有主要公司和许多小公司,也贡献了他们的努力。新标准的一个重要决定是,指定和标准化实际应用程序中有用的东西,借鉴现有成功的产品和项目的思想和理念。因此,Hibernate作为一个成功的数据持久化解决方案,在新标准的持久化部分扮演了重要的角色。但是Hibernate和EJB3之间的关系具体是什么,以及什么是Java Persistence呢?
1.理解标准
首先,难以(如果不是不可能)把规范和产品进行比较。问题应该是“Hibernate实现EJB 3.0规范吗?它对我的项目有什么影响?我必须使用其中一个吗?”
新的EJB 3.0规范有几个部分:第一部分给会话bean、消息驱动bean以及部署规则等,定义新的EJB编程模型。规范的第二部分专门处理持久化:实体、ORM元数据、持久化管理器接口和查询语言。第二部分被称作JPA,可能因为它的接口是在javax.persistence包中。本书将始终使用这个首字母的缩写。
这种分离也存在于EJB 3.0产品中,有些实现支持规范所有部分的一个完全的EJB 3.0容器,其他产品可能只实现Java Persistence部分。新标准中设计了两条重要的原则:
l JPA引擎应该是可插拔的,这意味着如果你不满意,应该能够从中取出一种产品并用另一种代替——即使你要保留相同的EJB 3.0容器或者Java EE 5.0应用程序服务器。
l JPA引擎应该能够在EJB 3.0(或者任何其他)运行时环境之外运行,而简单的标准Java中不需要容器。
这种设计的结果是,开发人员和架构师有了更多的选择,这样带动了竞争,因此提高了产品的整体质量。当然,实际的产品也提供超出规范的特性,作为特定于供应商的扩展(例如性能调优,或者因为供应商关注一个特定的垂直的问题领域)。
Hibernate实现Java Persistence,并且由于JPA引擎必须是可插拔的,新的和值得关注的软件结合成为可能。可以从不同的Hibernate软件模块中选择,并根据项目的技术和业务需求把它们结合起来。
2.Hibernate Core
Hibernate Core也称作Hibernate 3.2.x或者Hibernate。它是持久化的基础服务,带有原生的API和它存储在XML文件中的映射元数据。它有一种查询语言称作HQL(与SQL几乎相同),以及用于Criteria和Example查询的可编程查询接口。对于每个东西都有几百种选项和特性可用,因为Hibernate Core真正是所有其他模块创建的基础和平台。
Hibernate Core可以单独使用,独立于任何框架或者任何包含所有JDK的特定运行时环境。它适用于每一个Java EE/J2EE应用程序服务器、Swing应用程序、简单的servlet容器等。只要你能够给Hibernate配置数据源,它就能够实现。应用程序代码(在持久层中)将使用Hibernate API和查询,并且你的映射元数据编写在原生的Hibernate XML文件中。
原生的Hibernate API、查询和XML映射文件是本书的主要关注点,它们首先在所有代码示例中得以阐述。因为Hibernate功能是所有其他可用选项的一个超集(superset)。
3.Hibernate Annotations
JDK 5.0提供了定义应用程序元数据的一种新方法:类型安全的注解直接嵌入到Java源代码中。许多Hibernate用户已经熟悉这个概念,就像XDoclet软件在编译时支持Javadoc元数据属性和预处理程序一样(对于Hibernate,它产生XML映射文件)。
使用Hibernate Core顶部的Hibernate Annotations包,现在可以使用类型安全的JDK 5.0元数据作为原生的Hibernate XML映射文件的替代或者补充。一旦见过与Hibernate XML映射文件并排的映射注解时,你就会发现它的语法和语义很常见。然而,基础注解不是私有的。
JPA规范定义ORM元数据语法和语义,主要机制为JDK 5.0注解。(是的,Java EE 5.0和EJB 3.0需要JDK 5.0。)Hibernate Annotations一般来说是实现JPA标准的一组基础注解,它们也是更高级的和更奇异的Hibernate映射和调优所需的一组扩展注解。
可以使用Hibernate Core和Hibernate Annotations减少映射元数据的代码行,相比于原生的XML文件,你可能更喜欢注解更易重构的能力。如果完整的可移植性不是你最关注的,则可以只用JPA注解,或者增加一个Hibernate扩展注解。(在实际应用程序中,你应该相信已经选择的产品,而不是始终否认它的存在。)
本书将通篇讨论注解在开发过程中的影响,如何在映射中使用注解,以及原生的Hibernate XML示例。
4.Hibernate EntityManager
JPA规范也定义编程接口、持久化对象的生命周期规则和查询特性。JPA这部分的Hibernate实现可被用作Hibernate EntityManager,这是另一个可以堆在Hibernate Core顶部的可选模块。当需要简单的Hibernate接口或者甚至需要JDBC连接时,可以退回。Hibernate原生的特性在各个方面都是JPA持久化特性的一个超集。(简单的事实就是,Hibernate EntityManger是对提供JPA兼容性的Hibernate Core的一个小包装。)
使用标准化的接口和标准化的查询语言有个好处:可以使用任何EJB 3.0兼容应用程序服务器执行JPA兼容的持久层。或者,可以在简单的Java中任何特定的标准运行时环境之外使用JPA(这就是Hibernate Core可以被用在任何地方的真正含义)。
Hibernate Annotations应该与Hibernate EntityManager结合考虑。如果你针对JPA接口使用JPA查询编写应用程序代码,而没有用JPA注解创建大部分映射,这是不正常的。
5.Java EE 5.0应用程序服务器
本书不涵盖所有EJB 3.0,我们的重点自然是在持久化和规范的JPA部分。(当然,当谈到关于应用程序架构和设计时,我们将介绍许多托管EJB组件的技术。)
Hibernate也是JBoss应用程序服务器(JBoss AS)的一部分,是J2EE 1.4和(不久的)Java EE 5.0的一个实现。Hibernate Core、Hibernate Annotations和Hibernate EntityManager结合起来,形成了这个应用程序服务器的持久化引擎。因此,可以独立使用每件东西,也可以在应用程序服务器内部使用并享有所有EJB 3.0的益处,例如会话bean、消息驱动的bean和其他的Java EE服务。
要完成这部分的学习,还必须理解Java EE 5.0应用程序服务器不再是J2EE 1.4时代的庞然大物。事实上,JBoss EJB 3.0容器也是个可嵌入的版本,它在其他应用程序服务器的内部,甚至Tomcat或者单元测试里面,或者Swing应用程序中运行。第2章将准备一个利用EJB 3.0组件的项目,给简单的整合测试安装JBoss服务器。
如你所见,原生的Hibernate特性实现了规范的重要部分,或者是必要时提供额外功能的自然的供应商扩展。
有个简单的技巧,可以立即知道正在看的代码是JPA还是原生的Hibernate。如果只看见有javax.persistence.*导入,就是正处在规范内工作;如果也导入org.hibernate.*,就是正在使用原生的Hibernate功能。稍后会介绍一些更多的技巧,帮你清楚地把可移植的代码和特定于供应商的代码分开。
常见问题 Hibernate的前景如何?Hibernate Core将被独立开发,且比EJB 3.0或者Java Persistence规范更快。它将成为新思想进行试验的地方,就如它一向所做的那样。对于所有使用Hibernate Annotations和Hibernate EntityManager的Java Persistence用户来说,任何为Hibernate Core开发的新特性,都可以立即、自动地作为扩展使用。随着时间的过去,如果一个特定的概念已经证明是没用的,Hibernate开发人员将与其他专家组成员一起,为最新的EJB或者Java Persistence规范制订未来的标准。因此,如果你对快速演变的标准感兴趣,我们鼓励你使用原生的Hibernate功能,并发送反馈给相应的专家组。对整体可移植性的渴望和供应商扩展的排斥,是我们看到EJB 1.x和2.x停滞的主要原因。
对ORM和Hibernate进行了如此一番赞赏之后,应该来看一些实际的代码了。到了结束理论,并建立第一个项目的时候了。






