将对象映射到关系数据库:对象/关系映射(O/RMapping)详解大多数现代商业应用开发项目使用面向对象技术,比如采用Java或者C#来创建应用软件,同时使用关系型数据库来存储数据。但这并不是要说你没有其它选择,也有许多应用程序是使用面向过程的语言开发,比如COBOL,而且也有许多系统使用对象型数据库或者XML数据库来存储数据。然而,因为面向对象和关系数据库技术到目前为止已经成为一种事实上的标准,在本章节中我假设你正在使用这些技术。如果你采用其它的存储技术,本文里的许多概念仍然适用,只需要做一点点修改(不必担心,RealisticXML总括了对象与XML映射的相关问题)。在项目组通常用来创建以软件为基础的系统时,那些技术中,存在着面向对象技术和关系型技术之间的阻抗失配。不过这种阻抗失配很容易被克服,秘诀在于两点:你需要理解把对象映射到关系型数据库的过程,以及如何去实现这些映射。在本章节里,“映射”一词是用来表示如何把对象和对象之间的关系对应到数据库表以及表之间的关系。你将很快发现这并不像听起来的那样简单易懂,尽管它实际上也不是那么的槽糕。目录•敏捷DBA的角色•基本概念oShadow信息o映射元数据o如何使映射适合全过程•继承结构的映射o整个层次结构映射到一张表o每个具体类映射到单独的一张表o每个类单独映射到一张表o将类映射为一个通用的表结构o多重继承映射________________________________________满江红翻译团队:映射策略之间的比较•映射对象关系o关系的类型o如何实现对象关系o如何实现关系数据库中的关系o关系映射一对一映射一对多映射多对多映射o映射有序集合o映射递归关系•映射类作用域(Class-Scope)属性•性能调优o优化你的映射o延迟读取•为什么数据Schema不应该主导对象Schema•实现方式对对象的影响•模型驱动体系结构(MDA:ModelDrivenArchitecture)的含义•映射技术模式化•参考文献和阅读推荐1.敏捷DBA的角色图1显示了一个敏捷DBA在映射对象到关系数据库的过程中所扮演的角色。其中我们关心三个主要的活动。1.映射。基本的目标是决定一个有效的策略来持久化对象数据。这包括保存单个对象的属性以及对象之间的关联,同时也包括那些类之间的继承结构。2.实现映射3.性能调优在图1中我们注意到有一个有趣的事情,敏捷的DBA和应用程序开发人员在在这三个主要活动中都在一起工作。虽然敏捷的DBA应该确保映射的有效性,但他们实际上并不是独自对其负责的。与他人协同工作而不单打独斗正是敏捷软件开发成功的关键所在。图1.映射时敏捷DBA的角色。________________________________________满江红翻译团队:基本概念在学习如何把对象映射到关系型数据库的过程中,通常是从映射一个类的数据属性开始的。一个属性可以映射到关系型数据库里0个或者多个字段。请记住,不是所有的属性都是持久性的,其中的一些只是用做临时计算的。例如,在你的应用程序中一个Student对象可能需要有一个平均分(averageMark)属性,但并不需要存储到数据库里,因为它是由应用程序计算得到。一个对象的某些属性可能本身也是对象,比方说一个Customer对象拥有一个Address对象作为其属性——这其实反映了两个类之间需要被映射的关系,Address类本身也需要被映射。重要的是这是一个递归的定义:在需要的地方,一个属性将被映射到0个或者多个字段。昀简单的映射就是把一个属性映射到一个字段。当双方拥有一样的基本类型的时候,这甚至可以变得更简单。例如,双方都是date类型,或者属性是string类型而字段是char型,或者属性是number类型而字段是float类型。映射术语映射(动词).指的是如何把对象和对象之间的关系持久化到永久存储设备(这在里是关系型数据库)中的行为。映射(名词).如何将对象的属性或关系持久化到永久存储设备的定义的关系。属性.数据属性,是实际的物理属性,例如一个firstName字串;或者是由某个操作实现的虚拟属性,例如getTotal()方法返回一个订单的总数。________________________________________满江红翻译团队:属性映射.描述如何持久化对象的属性的映射。关系映射.描述如何持久化两个或者更多的对象之间的一个关系(关联,聚合或者组合)。把类映射到表上会让许多事情思考起来更简单,有时候的确是这样映射的,但并非总是这样直接,除了少数特别简单的数据库,你将不会有机会在类和表之间进行简单的一一映射。在这章的后面你将看到继承映射。但是,就这一整章全面来看,通常对于初始的映射,单类映射到单表是适用的(性能调优可能会促使你对映射进行重构)。现在,让我们从简单事物开始。图2显示了2个模型,一个UML的类图和一个遵循UML数据建模规则的物理数据模型。这两张图描绘了一个简单的订单系统。你可以看到如何映射类的属性到数据库的字段上。例如,图里显示Order类的dateFulfilled属性映射到Order表的dateFulfilled字段,OrderItem类的numberOrdered属性映射到OrderItem表的NumberOrdered字段。图2.简单映射的例子________________________________________满江红翻译团队:基本属性的映射很容易确定,有几个原因。首先,两个模型中使用相似的命名规则,这是采用敏捷建模实践“建模标准化”的一个方面;其次,通常是同一群人创建这两个模型。而当人们在不同的团队工作的时候,很容易做出不同的方案,即便是在各个团队本身的工作都很出色的时候也是这样,因为他们沿着不同的方向进行决策;第三,一个模型很容易用来驱动另一个模型的开发。在“不同的项目需要不同策略”一文中我讨论了当你创建一个新系统的时候,你的对象schema应该主导你的数据库schema的开发。图2里面显示的两个schema虽然很相似,但还是存在一些区别。这些区别意味着不存在一个完美的映射。2个schema间的不同点有:•在对象schema里,tax有多个属性而数据schema里只有一个。当对象被保存时,Order类里tax的3个属性将被相加并保存到tax字段里。然而,当对象被读进内存时,这3个属性将需要被计算(或者使用一个延迟初始化的方式,这样每个属性仅仅在第一次被访问的时候计算)。一个像这样的schema上的区别说明数据库schema需要重构,把tax字段分成3个不同的字段。•数据Schema标明了键而对象Schema没有。表中的每一行都有一个全表唯一的主键值,行间的关系被用外键实现。而对于对象之间的关系,是通过使用引用而非使用外键。这暗示为了完整的持久化对象和它们的关系,对象需要知道数据库里面用来标识它们的键值。这些额外的信息被称为“shadow信息”。•每个Schema里面使用了不同的类型。Order里subTotalBeforeTax属性是Currency类型,而Order表里subTotalBeforeTax字段是float型。当你实现这个映射,你需要在这些数据的表示形式之间进行无损转换。2.1Shadow信息Shadow信息是指那些为了将对象持久化,而不得不维持的非业务数据。这通常包括主键信息,特别是当主键是没有业务含义的代理键值时;并发控制标识例如时间戳或者增量计数器;以及那些版本号。例如,在图2你可以看到Order表有一个OrderID的字段作为主键,一个Order类所没有的LastUpdate字段被用来乐观并发控制。为了正确持久化一个order对象,就需要实现包含这些信息的shadow属性。图3显示一个对Order和OrderItem进行详细设计的类模型。和图2相比,有一些修改。首先,新的图显示了类需要正确持久化自己所需要的shadow属性。shadow属性是实现可见的,在它们的名字前面是一个空格而不是一个“-”号,同时被指定了persistence进行说明(这不是一个UML标准)。第二,它显示需要在两个类之间实现联系所需要的辅助(scaffolding)属性。辅助属性,例如Order里面的orderItems列表,同样是实现可见的。第三,一个getTotalTax()操作需要被加到Order类里来计算Order表中tax字段所需要的值。这是为什么我用属性映射这个词来代替属性映射-你所想要做的是映射一个类里面的属性到数据库里的字段上,有时这些属性是通过简单的属性实现的,而其它某些时候是由一个或者多个操作所决定的。________________________________________满江红翻译团队:类型的标志来表示当前一个对象是否存在于数据库中。这里的问题是当你把数据保存到一个关系型数据中,如果原先的对象是从数据库中获取出来的,你需要使用一个SQLupdate语句来保存数据,否则应该使用SQLinsert语句。一个普通的解决方法是为每个类实现一个isPersistent的boolean型信号标志(图3里没有显示),当数据是从数据库里面读取的时候把它的值设置成true,如果对象是新创建的话则设置为false。在UML社区里面的一个通用的风格约定是在类图里不显示shadow信息,例如键值或并发标识。类似的,通常也不显示支撑代码。因为每个人都知道你需要做这种事情,所以何必浪费时间去显示这些明显的事实呢?Shadow信息不必用业务对象(businessobject)来实现,不过那样你的程序就要在其他地方处理这个问题。例如,在EnterpriseJavaBeans(EJBs)里你把主键保存在EJB以外的主键类(primarykeyclass)里,独立对象引用相关的主键对象。而进一步的,JavaDataObject(JDO)则是在JDOs里面实现shadow信息,而不是在业务对象(businessobject)里。2.2映射元数据图4显示了元数据(metadata),这些是代表持久化图3里Order和OrderItem类所需要的属性映射。元数据是关于数据的信息。因如下原因使得图4显得很重要的。首先,我们需要某个方式来表现映射。我们可以把2个schema并排放在一起,就像图2里面那样,然后在它们之间画线,但是这很快会变的非常复杂,而另外一个选择是像图4里面这样列表;第二,映射元数据的概念对持久化框架的功能是非常重要的,这是一个可以让敏捷数据库技术发挥作用的数据库封装策略。图4.代表属性映射的元数据PropertyColumnOrder.orderIDOrder.OrderID________________________________________满江红翻译团队:()Order.TaxOrder.subtotalBeforeTaxOrder.SubtotalBeforeTaxOrder.shipTo.personIDOrder.ShipToContactIDOrder.billTo.personIDOrder.BillToContactIDOrder.lastUpdateOrder.LastUpdateOrderItem.orderedOrderItem.OrderIDOrder.orderItems.position(orderItem)OrderItem.ItemSequenceOrderI