Hibernate041013.docx
- 文档编号:26266726
- 上传时间:2023-06-17
- 格式:DOCX
- 页数:29
- 大小:142.16KB
Hibernate041013.docx
《Hibernate041013.docx》由会员分享,可在线阅读,更多相关《Hibernate041013.docx(29页珍藏版)》请在冰豆网上搜索。
Hibernate041013
第一章理解对象-关系持续性
在我们工作的每个软件项目工程中,管理持续性数据的方法已经成为一项关键的设计决定。
对于Java应用,持续性数据并不是一个新的或不寻常的需求,你也许曾经期望能够在许多相似的,已被很好构建的持续性解决方案中简单地进行选择。
考虑一下Web应用框架(JakartaStruts对WebWork),GUI组件框架(Swing对SWT),或模版工具(JSP对Velocity)。
每一种相互竞争的解决方案都有其优缺点,但它们至少都共享了相同的范围与总体的方法。
不幸的是,这还不是持续性技术的情形,对持续性技术相同的问题有许多不同的混乱的解决方案。
在过去的几年里,持续性已经成为Java社区里一个争论的热点话题。
对这个问题的范围许多开发者的意见甚至还不一致。
持续性还是一个问题吗?
它早已被关系技术与其扩展例如存储过程解决了。
或者它是一个更一般的问题,必须使用特殊的Java组件模型例如EJB实体Bean来处理?
甚至SQL和JDBC中最基本的CRUD(create,read,update,delete)操作也需要进行手工编码,还是让这些工作自动化?
如果每一种数据库管理系统都有它自己的方言,我们如何达到可移植性?
我们应该完全放弃SQL并采用一种新的数据库技术,例如面向对象数据库系统吗?
争论仍在继续,但是最近一种称作对象-关系映射(ORM)的解决方案逐渐地被接受。
Hibernate就是这样一种开源的ORM实现。
Hibernate是一个雄心勃勃的项目,它的目标是成为Java中管理持续性数据问题的一种完整的解决方案。
它协调应用与关系数据库的交互,让开发者解放出来专注于手中的业务问题。
Hibernate是一种非强迫性的解决方案。
我们的意思是指在写业务逻辑与持续性类时,你不会被要求遵循许多Hibernate特定的规则和设计模式。
这样,Hibernate就可以与大多数新的和现有的应用平稳地集成,而不需要对应用的其余部分作破坏性的改动。
本书是关于Hibernate的。
我们包含了基本与高级的特征,并且描述了许多使用Hibernate开发新应用时的推荐方式。
通常这些推荐并不特定于Hibernate——有时它们可能是我们关于使用持续性数据工作时处理事情的最佳方式的一些想法,只不过在Hibernate的环境中进行了介绍。
然而,在我们可以开始使用Hibernate之前,你需要理解对象持续性和对象-关系映射的核心问题。
本章解释了为什么像Hibernate这样的工具是必需的。
首先,我们定义了在面向对象的应用环境中持续性数据的管理,并且讨论了SQL,JDBC和Java的关系,Hibernate就是在这些基础的技术与标准之上构建的。
然后我们讨论了所谓的对象-关系范例不匹配的问题和使用关系数据库进行面向对象的软件开发中所遇到的一些一般性的问题。
随着这个问题列表的增长,我们需要一些工具与模式来最小化我们用在与持续性有关的代码上的时间就变得很明显了。
在我们查看了可选的工具和持续性机制后,你会发现ORM在许多情况下可能是最好的解决方案。
我们关于ORM的优缺点的讨论给了你一个完整的背景,在你为自己的项目选择持续性解决方案时可以作出最好的决定。
最好的学习方式并不必须是线性的。
我们猜想你可能想立刻尝试一下Hibernate。
如果这的确是你喜欢的方式,请跳到第2章第2.1节“开始”,在那儿我们进入并开始编写一个(小的)Hibernate应用。
不读这一章你也可能理解第2章,但我们还是推荐你在循环通读本书的某一时刻再回到这一章。
那样,你就可以准备好并且具有了学习其余资料所需的所有的背景概念。
1.1什么是持续性?
几乎所有的应用都需要持续性数据。
持续性在应用开发中是一个基本的概念。
如果当主机停电时一个信息系统没有保存用户输入的数据,这样的系统几乎没有实际的用途。
当我们讨论Java中的持续性时,我们通常是指使用SQL存储在关系数据库中的数据。
我们从简单地查看一下这项技术和我们如何在Java中使用它开始。
具有了这些知识之后,我们继续关于持续性的讨论以及如何在面向对象的应用中实现它。
1.1.1关系数据库
你,像许多其他的开发者,很可能在使用一个关系数据库进行工作。
实际上,我们中的大多数每天都在使用关系数据库。
关系技术是一个已知数。
仅此一点就是许多组织选择它的一个充分的理由。
但仅仅这样说就有点欠考虑了。
关系数据库如此不易改变不是因为偶然的原因而是因为它们那令人难以置信的灵活与健壮的数据管理方法。
关系数据库管理系统既不特定于Java,也不特定于特殊的应用。
关系技术提供了一种在不同的应用或者相同应用的不同技术(例如事务工具与报表工具)之间共享数据的方式。
关系技术是多种不同的系统与技术平台之间的一个共同特征。
因此,关系数据模型通常是业务实体共同的企业级表示。
关系数据库管理系统具有基于SQL的应用编程接口,因此我们称今天的关系数据库产品为SQL数据库管理系统,或者当我们讨论特定系统时,称之为SQL数据库。
1.1.2理解SQL
为了有效地使用Hibernate,对关系模型和SQL扎实的理解是前提条件。
你需要用你的SQL知识来调节你的Hibernate应用的性能。
Hibernate会自动化许多重复的编码任务,如果你想利用现代SQL数据库的全部能力,你的持续性技术的知识必须扩充至超过Hibernate本身。
记住基本目标是健壮地有效地管理持续性数据。
让我们回顾一些本书中用到的SQL术语。
你使用SQL作为数据定义语言(DDL)通过CREATE与ALTER命令来创建数据库模式。
创建了表(索引,序列等等)之后,你又将SQL作为数据处理语言(DML)来使用。
使用DML,你执行SQL操作来处理与取出数据。
处理操作包括插入,更新和删除。
你使用约束,投射,和连接操作(包括笛卡尔积)执行查询来取出数据。
为了有效地生成报表,你以任意的方式使用SQL来分组,排序和合计数据。
你甚至可以相互嵌套SQL命令,这项技术被称作子查询。
你也许已经使用了多年的SQL并且非常熟悉用它编写的基本操作和命令。
尽管如此,我们的经验还是告诉我们SQL有时难于记忆并且有些术语有不同的用法。
为了理解本书,我们不得不使用相同的术语与概念;因此,如果我们提到的任何术语是新出现的或者是不清楚的,我们建议你看一下附录A。
为了进行健全的Java数据库应用开发,SQL知识是强制的。
如果你需要更多的资料,找一本DanTow的优秀著作《SQLTuning》。
同时读一下《AnIntroductiontoDatabaseSystems》,学习一些(关系)数据库系统的理论,概念和思想。
关系数据库是ORM的一部分,当然另一部分由你的Java应用中的对象构成,它们需要使用SQL被持续化到数据库中。
1.1.3在Java中使用SQL
当你在Java应用中使用SQL工作时,Java代码通过Java数据库连接(JDBC)执行SQL命令来操作数据库。
SQL可能被手工编码并嵌入到Java代码中,或者可能被匆忙地通过Java代码生成。
你使用JDBCAPI将变量绑定到查询参数上,开始执行查询,在查询结果表上滚动,从结果集中取出值,等等。
这些都是底层的数据访问任务,作为应用开发者,我们对需要这些数据访问的业务问题更加感兴趣。
需要我们自己来关心这些单调的机械的细节,好像并不是确定无疑的。
我们真正想做的是能够编写保存和取出复杂对象(我们的类实例)的代码—从数据库中取出或者保存到数据库中,尽量为我们减少这些底层的苦差事。
因为这些数据访问任务通常都是非常单调的,我们不禁要问:
关系数据模型特别是SQL是面向对象的应用中持续性问题的正确选择吗?
我们可以立即回答这个问题:
是的!
有许多原因使SQL数据库支配了计算行业。
关系数据库管理系统是唯一被证明了的数据管理技术并且几乎在任何Java项目中都是一项需求。
然而,在过去的15年里,开发者一直在讨论范例不匹配的问题。
这种不匹配解释了为什么每个企业项目都需要在持续性相关的问题上付出如此巨大的努力。
这里所说的范例是指对象模型和关系模型,或者可能是面向对象编程与SQL。
让我们通过询问在面向对象的应用开发环境中,持续性究竟意味着什么,来开始我们对不匹配问题的探究。
首先,我们将本章开始部分声明的对持续性过分简单的定义扩展到一个较宽的范围,更成熟的理解包括维护与使用持续性数据。
1.1.4面向对象应用中的持续性
在面向对象的应用中,持续性允许一个对象的寿命可以超过创建它的程序。
这个对象的状态可能被存储到磁盘上,并且在将来的某一时刻相同状态的对象可以被重新创建。
这样的应用不仅仅限于简单的对象——关联对象的完整图形也可以被持续化并且以后可以在新的进程里被重新创建。
大多数对象并不是持续性的;暂态对象只有有限的寿命,被实例化它的进程的寿命所限定。
几乎所有的Java应用都在混合使用持续与暂态对象;因此,我们需要一个子系统来管理我们的持续性数据。
现代的关系数据库为持续性数据提供了一种结构化的表示方法,允许排序,检索和合计数据。
数据库管理系统负责管理并发性和数据的完整性;它们负责在多个用户和多个应用之间共享数据。
数据库管理系统也提供了数据级别的安全性。
当我们在本书中讨论持续性时,我们考虑以下这些事情:
■存储,组织与恢复结构化数据
■并发性与数据完整性
■数据共享
特别地,我们将在使用域模型的面向对象的应用环境中考虑这些问题。
使用域模型的应用并不直接使用业务实体的扁平表示进行工作,这些应用有它们自己的面向对象的业务实体模型。
如果数据库中有“项目”与“竞价”表,则在这些Java应用中会定义“项目”与“竞价”类。
然后,业务逻辑并不直接在SQL结果集的行与列上进行工作,而是与面向对象的域模型进行交互,域模型在运行时表现为一个关联对象的交互图。
业务逻辑从不在数据库中(作为SQL存储过程)执行,而是被实现在Java程序中。
这就允许业务逻辑使用成熟的面向对象的概念,例如继承与多态。
我们可以使用众所周知的设计模式例如“策略”,“中介者”和“组合”,所有这些模式都依赖于多态方法调用。
现在给你一个警告:
并不是所有的Java应用都是按照这种方式设计的,并且也不打算是。
对于简单的应用不使用域模型可能会更好。
SQL和JDBCAPI可以完美地处理纯扁平数据,并且现在新的JDBC结果集已经使CRUD操作变得更简单了。
使用持续性数据的扁平表示进行工作可能更直接并且更容易理解。
然而,对于含有复杂业务逻辑的应用,域模型有助于有效地提高代码的可重用性和可维护性。
在本书中我们集中在使用域模型的应用上,因为通常Hibernate和ORM总是与这种类型的应用有关。
如果我们再次考虑SQL和关系数据库,我们最终会发现这两种范例的不匹配之处。
SQL操作例如投射与连接经常会导致对结果数据的扁平表示。
这与在Java应用中用来执行业务逻辑的关联对象的表示完全不同!
这是根本不同的模型,而不仅仅是相同模型的不同的显示方式。
带着这些认识,我们能够开始看一下这些问题——许多已经很好理解的与没有很好理解的——必须被合并使用这两种数据表示的应用所解决——一个面向对象的域模型与一个持续性的关系模型。
让我们仔细地看一下。
1.2范例的不匹配
范例不匹配的问题可以被分解成几部分,我们将依次进行分析。
让我们通过一个独立于问题的简单例子开始我们的研究。
随后当我们构建它时,你就会看到不匹配性问题的出现。
假设你需要设计与实现一个在线电子商务应用。
在这个应用中你需要一个类来表示系统用户的信息,另一个类用来表示用户账单的详细资料(参见图1.1)。
(图1.1)
看一下这个范例,你可以看到一个用户可以有多份账单资料,你可以在两个方向上对这两个类之间的关系进行导航。
一开始,表示这些实体的类可能会非常简单:
publicclassUser{
privateStringuserName;
privateStringname;
privateStringaddress;
privateSetbillingDetails;
...
}
publicclassBillingDetails{
privateStringaccountNumber;
privateStringaccountName;
privateStringaccountType;
privateUseruser;
...
}
注意,我们仅对与持续性有关的实体状态感兴趣,因此我们省略了属性存取方法与业务方法(例如getUserName()或billAuction())的实现。
非常容易给这个例子提出一个好的SQL表设计:
createtableUSER(
USERNAMEVARCHAR(15)NOTNULLPRIMARYKEY,
NAMEVARCHAR(50)NOTNULL,
ADDRESSVARCHAR(100)
)
createtableBILLING_DETAILS(
ACCOUNT_NUMBERVARCHAR(10)NOTNULLPRIMARYKey,
ACCOUNT_NAMEVARCHAR(50)NOTNULL,
ACCOUNT_TYPEVARCHAR
(2)NOTNULL,
USERNAMEVARCHAR(15)FOREIGNKEYREFERENCESUSER
)
这两个实体之间的关系通过外键来表示,表BILLING_DETAILS中的USERNAME就是外键。
对于这个简单的对象模型,对象-关系的不匹配几乎不明显;你可以直接编写JDBC代码来插入,更新和删除用户与账单资料的信息。
现在,让我们看一下当我们考虑一个更现实的问题时会发生什么。
当我们在我们的应用中加入更多的实体和实体关系时,范例不匹配的问题会变得更明显。
在我们当前的实现中最明显的问题是我们将地址设计成了一个简单的字符串值。
在大多数系统里,并不需要分别保存街道、城市、州、国家和邮政编码等信息。
当然,我们可以直接在用户类里增加这些属性,但是因为系统中其它的类也非常可能含有地址信息,创建一个地址类可能更有意义。
更新后的对象参见图1.2。
(图1.2)
我们同样需要增加一个地址表吗?
不需要。
通常只需将地址信息作为单独的列保存到用户表里。
这样的设计可能执行起来效率更高,因为在一个单独的查询里我们不需要通过表连接来取得用户和地址。
最好的解决方案可能是创建一个用户定义的SQL数据类型来表示地址,并且在用户表里使用这种类型的一个单独的列而不是许多新列。
基本上,我们有增加几列或者使用单独的一列(以一种新的SQL数据类型)这两种选择。
这明显是一个粒度的问题。
1.2.1粒度的问题
粒度是指你使用的对象的相对大小。
当我们讨论Java对象和数据库表时,粒度问题意味着持续性对象可以以不同的粒度来使用表和列,而表和列本身都有粒度上固有的限制。
让我们回到我们的例子。
增加一种新的数据类型,将Java地址对象在我们的数据库中保存为单独的一列,听起来好像是最好的方法。
毕竟,Java中的一个新的地址类与SQL数据类型中的一个新的地址类型可以保证互用性。
然而,如果你检查现在的数据库管理系统对用户定义列类型(UDT)的支持,你将会发现各种各样的问题。
对传统SQL而言,UDT支持是许多所谓的对象-关系扩展中的一种。
不幸的是,UDT支持在大多数SQL数据库管理系统中都是有些晦涩的特征,当然在不同的系统之间也不具有可移植性。
SQL标准支持用户定义的数据类型,但是少的可怜。
因为这个原因或者(任何)其它原因,此时在本项工作中使用UDT还不是一项共通的实践——并且你不可能遇到一个大量使用了UDT的遗留系统。
因此我们不能将我们新的地址类的对象作为一个等价的用户定义的SQL数据类型保存到一个独立的新列中。
对这个问题我们的解决办法是使用多个列,将它们定义成厂方定义的SQL类型(例如布尔,数值与字符串数据类型)。
同时考虑到表的粒度,用户表通常如下定义:
createtableUSER(
USERNAMEVARCHAR(15)NOTNULLPRIMARYKEY,
NAMEVARCHAR(50)NOTNULL,
ADDRESS_STREETVARCHAR(50),
ADDRESS_CITYVARCHAR(15),
ADDRESS_STATEVARCHAR(15),
ADDRESS_ZIPCODEVARCHAR(5),
ADDRESS_COUNTRYVARCHAR(15)
)
这导致了下面的发现:
我们的域对象模型中的类按照不同的粒度级别排列起来——从象用户这样的粗粒度的实体类到一个象地址这样的细粒度类,直到一个象邮政编码这样的简单的字符串属性值。
相比之下,在数据库的级别中只有两种粒度的级别是可见的:
表例如用户和数量列例如邮政编码。
这明显不如我们的Java类型系统灵活。
许多简单的持续性机制未能识别这种不匹配性因此不再限制对象模型上的更不灵活的表示。
我们曾经无数次地看到在用户类中包含一个邮政编码的属性。
可以证明粒度问题并不是特别难以解决。
的确,我们甚至可能都不列出它,之所以列出是因为在许多现有系统中它都非常明显这个事实。
我们将在第3章,第3.5节“细粒度的对象模型”中描述这个问题的解决方案。
当我们考虑使用了继承的域对象模型时,一个更困难并且更有趣的问题就会出现。
继承是面向对象设计的一个特征,我们可以用它以一种更新更有趣的方式给我们的电子商务应用的用户开账单。
1.2.2子类型的问题
在Java中,我们使用超类与子类实现继承。
为了介绍这为什么会出现不匹配性的问题,让我们继续构建我们的例子。
扩展一下我们的电子商务应用以使我们现在不仅能接受银行账户账单,还能接受信用卡与借记卡。
因此我们有几种方法可以给一个用户账户开账单。
在我们的对象模型中反映这个改变的最普通的方式是继承账单详细资料类。
我们可能有一个抽象的账单详细资料的超类和几个具体的子类:
信用卡,直接借记,支票等等。
这些子类中的每一个都会定义一些稍微不同的数据(与处理这些数据的完全不同的功能)。
图1.3的UML类图介绍了这个对象模型。
(图1.3)
我们立刻就会注意到SQL没有提供对继承的直接支持。
我们不能够通过编写“CREATETABLECREDIT_CARD_DETAILSEXTENDSBILLING_DETAILS(...)”将表CREDIT_CARD_DETAILS声明为表BILLING_DETAILS的一个子类型。
在第3章第3.6节“映射类继承”里,我们讨论了象Hibernate这样的对象-关系映射解决方案如何解决将类层次持续化到数据库表中的问题。
在社区里这个问题已经被很好地理解了,并且大多数的解决方案支持近似相同的功能。
但是对于持续性问题我们还没有完全结束——只要我们将继承引入到对象模型中,就会出现多态的可能性。
用户类与账单详细资料超类有一个关联,这是一个多态性的关联。
运行时,一个用户对象可能与账单详细资料的任何子类的实例相关联。
相似地,我们希望能够针对账单详细资料类编写查询并且让它返回子类的实例。
这个特征被称作多态性查询。
因为SQL数据库不支持继承的概念,它们没有明显的表示多态性关联的方式也不会太令人惊讶。
一个标准的外键约束精确地引用一个表;定义一个引用多个表的外键并不是那么简单。
我们可能以Java(与其它面向对象语言)的输入不如SQL那么严格来解释这个问题。
幸运地是,我们将在第3章介绍两种继承映射的解决方案,它们被设计为用来解决多态性关联的表示和有效地执行多态性查询。
因此,子类型不匹配是指Java模型中的继承结构必须被持续化到SQL数据库中,而SQL数据库并没有提供一个支持继承的策略。
不匹配问题的另一方面是对象的同一性问题。
你可能已经注意到了我们将用户表中的用户名定义成了主键。
那是一个好的选择吗?
与你下面将要看到的一样,那的确不是。
1.2.3同一性的问题
虽然对象同一性的问题一开始可能并不明显,但在我们不断增长与扩展的电子商务系统的例子中我们会经常遇到它。
这个问题在我们考虑两个对象(例如,两个用户)并且检查它们是否是同一个时就会看到。
有三种方法可以解决这个问题,两种使用Java,一种使用我们的SQL数据库。
与你预期的一样,想让它们协同工作需要提供一些帮助。
Java对象定义了两种不同的相等性的概念:
■对象同一性(粗略的等同于内存位置的相等,使用a==b检查)
■通过equals()方法的实现来决定的相等性(也被称作值相等)
另一方面,数据库行的同一性使用主键值表示。
象你将在第3.4节“理解对象同一性”中看到的一样,主键既不必然地等同于“equals()”也不等同于“==”。
它通常是指几个对象(不相同的)同时表示了数据库中相同的行。
而且,为一个持续类正确地实现equals()方法包含许多微妙的难点。
让我们通过一个例子讨论另一个关于数据库同一性的问题。
在我们用户表的定义中,我们已经使用了用户名作为主键。
不幸的是,这个决定使改变一个用户名变得很困难:
我们不仅需要更新用户表中的用户名列,而且也要更新账单详细资料表中的外键列。
因此,在本书后面的部分,我们推荐你无论什么情况如果可能的话都可以使用代替键。
代替键是指对用户没有意义的主键列。
例如,我们可以象这样改变我们的表定义:
createtableUSER(
USER_IDBIGINTNOTNULLPRIMARYKEY,
USERNAMEVARCHAR(15)NOTNULLUNIQUE,
NAMEVARCHAR(50)NOTNULL,
...
)
createtableBILLING_DETAILS(
BILLING_DETAILS_IDBIGINTNOTNULLPRIMARYKEY,
ACCOUNT_NUMBERVARCHAR(10)NOTNULLUNIQUE,
ACCOUNT_NAMEVARCHAR(50)NOTNULL,
ACCOUNT_TYPEVARCHAR
(2)NOTNULL,
USER_IDBIGINTFOREIGNKEYREFERENCESUSER
)
列USER_ID和BILLING_DETAILS_ID包含系统生成的值。
引入这些列纯粹是为了关系数据模型的好处。
它们如何(即使从不)在对象模型中表示?
我们将在第3.4节讨论这个问题并且找到一个对象-关系映射的解决方案。
在持续性环境中,同一性与系统如何处理缓存和事务密切相关。
不同的持续性解决方案选择不同的策略,这已经成为混乱的一方面。
我们包含了所有这些有趣的主题——并显示了他们是如何相关的——在第5章。
我们已经设计和实现的电子商务应用的构架很好地达到了我们的目的。
我们识别出了映射粒度,子类型和对象同一性这些不匹配性的问题。
我们几乎准备好了转移到应用的其它部分。
但是首先,我们需要讨论一下关联这个重要的概念——也就是说,我们的类之间的关系是如何被映射和处理的。
数据库中的外键就是我们所需要的全部吗?
1.2.4与关联有关的问题
在我们的对象模型中,关联描述了实体间的关系。
你记得用户,地址和账单详细资料类都是相互关联的。
与地址不同,账单详细资料依赖于自身。
账单详细资料对象保存到它们自己的表中。
在任何对象持续性解决方案中,实体关联的映射与管理都是中心概念。
面向对象的语言使用对象引用和对象引用的集合表示关联。
在关系世界里,关联被表示为外键列,外键是几个表的键值的拷贝。
这两种表示之间有些微妙的不同。
对象引用具有固有的方向性,关联是指从一个对象到另一个对象的引用。
如果对象间的关联可以在两个方向上进行导航,你需要定义两次关联,在每个关联类上分别定义一次。
这你早已在我们的对象模型类中见过了:
publ
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Hibernate041013