acm-header
登录

ACM通信

实践

动态语言中的ORM


技术要素图

作者:Dave Bollinger

大多数企业应用程序的一个主要组件是在关系数据库中传输对象的代码。最简单的解决方案通常是使用ORM(对象-关系映射)框架,该框架允许开发人员以声明的方式定义对象模型和数据库模式之间的映射,并根据对象表示数据库访问操作。这种高级方法大大减少了需要编写的数据库访问代码的数量,提高了开发人员的工作效率。

目前有几种ORM框架在使用。例如,Hibernate,2TopLink,11和OpenJPA1框架在Java开发人员中很流行,还有NHibernate10被许多。net开发人员使用。最近受到企业开发人员大量关注的两个较新的ORM框架是Active Record15为Ruby14和GORM (Grails对象关系映射)12Groovy。7这些新框架不同于传统的ORM框架,因为它们是用动态语言编写的,允许在运行时创建新的程序元素。活动记录和GORM使用这些动态功能的方式可以显著简化应用程序。

本文将介绍GORM是如何工作的。它对GORM和Hibernate进行了比较和对比,主要集中在三个方面:定义对象-关系映射;对持久对象执行基本的保存、加载和删除操作;以及执行查询。它描述了GORM如何利用Groovy的动态特性来提供一种不同风格的ORM,这种ORM有一些限制,但对许多应用程序来说更容易使用。

回到顶部

Groovy、Grails和GORM

GORM是Grails的持久性组件,Grails是一个旨在简化Web开发的开源框架。Grails是用Groovy编写的,Groovy是一种动态的、面向对象的语言,运行在JVM (Java虚拟机)上。因为Groovy可以与Java无缝地互操作,Grails可以利用几个成熟的Java框架。特别是,GORM使用Hibernate,这是一个流行的健壮的ORM框架。

然而,GORM不仅仅是Hibernate框架的一个简单包装器。相反,它提供了一种非常不同的API。GORM在两个方面有所不同。首先,Groovy语言的动态特性使GORM能够做静态语言中不可能做的事情。其次,在Grails中普遍使用CoC(约定优于配置)减少了使用GORM所需的配置量。让我们更详细地看看每一个原因。

Groovy的动态。GORM严重依赖于Groovy语言的动态功能。特别是,它广泛地利用了Groovy在运行时定义方法和属性的能力。在Java等静态语言中,属性访问或方法调用是在编译时解析的。相比之下,Groovy直到运行时才解析属性访问和方法调用。Groovy应用程序可以动态地定义方法和属性。

Groovy提供了两种不同的方法来在运行时向类添加方法和属性。最简单的方法是定义propertyMissing ()methodMissing ()方法。的propertyMissing ()方法在应用程序试图访问未定义的属性时由Groovy运行时调用。类似地,methodMissing ()方法在应用程序调用未定义的方法时调用。这些方法使对象的行为如同属性或方法存在一样。

第二种更复杂的方法是使用奇妙的命名ExpandoMetaClass.每个Groovy类都有一个元类属性,返回一个ExpandoMetaClass.应用程序可以通过操作这个元类向类添加方法或属性。例如,图1是向String类添加方法的代码片段,该方法将字符串与其本身连接起来。

该代码段获取字符串元类并赋值给它doubleString属性一个实现新方法的闭包(一种匿名方法)。

Groovy应用程序经常使用methodMissing ()而且ExpandoMetaClass在一起。第一次调用未定义的方法时,missingMethod ()方法定义方法ExpandoMetaClass.下一次,直接调用新定义的方法,从而绕过相对昂贵的方法missingMethod ()机制。

稍后您将看到Grails如何使用methodMissing ()而且ExpandoMetaClass在运行时将与持久性相关的方法和属性注入到域类中,从而简化应用程序代码。

约定优于配置。GORM的第二个关键思想是CoC。它的前提是框架应该有合理的默认值,不应该要求开发人员显式地配置每个方面;相反,只有例外情况才需要配置。CoC最初是由Rails和Grails框架普及的,但主流的Java EE框架包括Spring16已经开始接受这个概念。今天,开发人员希望现代Java EE框架比旧框架需要的配置少得多。

CoC在Grails中使用。例如,内置默认值决定如何将HTTP请求映射到处理程序类。类似地,GORM也有规则来定义要持久化哪些类,以及如何包含列和表名的默认值。由于使用了CoC,典型的Grails应用程序包含的配置代码和元数据要比使用传统框架的应用程序少得多。

现在我们已经了解了GORM的关键基础,让我们学习如何使用它。

回到顶部

GORM映射

使用ORM框架的一个关键部分是指定对象模型如何映射到数据库。开发人员必须指定类如何映射到表、属性如何映射到列以及关系如何映射到外键或连接表。本节将介绍如何使用传统ORM框架实现此功能,以及如何在Grails中完成此功能。

使用XML和注释进行映射。Java类的持久状态是它的字段或属性。字段相当于Java中的实例变量。属性是由跟随javabean的getter和setter方法定义的6命名约定。例如,getFoo ()而且setFoo ()定义属性喷火.getter和setter方法通常提供对与属性同名的字段的访问,尽管不是必须这样做。

Hibernate应用程序可以使用XML或注释将域类的字段或属性映射到数据库模式。图2左边显示一个注释示例,右边显示一个XML示例。的字段持久化客户类,但是应用程序可以通过注释getter方法或从XML文档中省略默认访问属性来保存属性。

XML和注释生成等价的元数据。它们都指定了客户类是持久的。它们还指定Hibernate应该使用任何适合底层数据库的机制生成对象的主键,并将其存储在id字段。的版本字段配置为存储hibernate维护的版本号。它们都持久化了name字段并指定accounts字段表示一对多关系。

XML和注释对表名和列名都有默认值。表名默认为类的名称,列名默认为属性的名称。您可以使用额外的注释或XML属性和元素覆盖这些默认值。方法指定表名@ table的名称属性<课程>元素。

每种方法都有优点和缺点。XML优于注释的一个优点是它将O/R映射从Java代码中分离出来,从而将域类与Hibernate解耦。这种分离的一个问题是使映射和代码保持同步变得更加困难。XML也比注释更啰嗦。此外,XML映射必须显式列出类的所有持久属性,而某些基本类型的字段,如Customer.name在使用注释时自动持久。

另一个问题是,无论您使用的是XML还是注释,您通常都需要添加字段来存储主键和版本号。主键字段通常是Hibernate或域对象的客户机所需要的。版本号用于乐观锁定。然而,这些字段的问题在于,应用程序的业务逻辑通常不需要它们。它们必须被添加到每个域类中以支持持久性。

GORM中的O/R映射。在定义ORM时,Grails严重依赖约定而不是配置。它自动将grails app/ domain目录中的类视为持久化的。GORM自动持久化每个类的属性。它默认来自类名和属性名的表名和列名。GORM还向每个类添加主键和版本号属性。

下面是一个域类示例。Customer类有一个名为name的字段。此外,由于该字段具有默认可见性,Groovy通过定义name属性自动定义getName ()而且setName ()方法。

ins01.gif

GORM自动将Customer类映射到Customer表,并将的名字属性设置为名称列。GORM增加了id属性设置为类,并将其映射到名为id.它还增加了一个版本属性并将其映射到版本列。与传统的ORM框架不同,只要数据库模式与默认值匹配,GORM只需要很少的配置。

GORM的另一个很好的特性是,它将维护域模型类的创建和最后更新时间。你只需要定义lastupdate而且dateCreated属性,GORM将自动更新它们。相比之下,在使用普通Hibernate时必须编写代码来完成此任务。

GORM还通过使用静态属性以类似于其他语言注释的方式提供元数据,使映射关系变得容易。例如,static属性hasMany为域类定义一对多关系。的值hasMany财产是一张地图。每个映射条目定义一对多关系:它的键是存储集合的属性的名称,它的值是集合元素的类。对于每个一对多关系,GORM添加一个属性来存储对象集合,以及用于维护该关系的方法。

对象之间映射一对多关系的示例如下客户类和账户类。

ins02.gif

帐户集合存储在一个名为账户, GORM将其添加到客户类。使用外键映射该关系customer_id在帐户表中。的belongsTo属性指定客户拥有该帐户,删除客户时应删除该帐户。

GORM还动态定义了两个方法来管理这种关系。的addToAccounts ()方法将帐户添加到集合中removeFromAccounts ()方法将删除帐户。这些方法还维护从的逆关系账户客户.通过自动定义这些方法(否则必须手工编写),GORM简化了代码,降低了出错的可能性。

配置映射。CoC减少了所需的配置量。但是,有时需要指定ORM的某些方面。例如,表名或列名可能与默认值不匹配,或者类的派生属性不应该被持久化。为了支持这些需求,GORM允许您指定ORM的各个方面。然而,GORM没有使用XML或注释等不同的配置语言,而是在域类中使用Groovy代码片段。

下面是一个示例,说明如何覆盖默认的表和列名,并指定不应持久保存某个属性。

ins03.gif

在本例中,瞬变属性,它是属性名称的列表,指定属性,该属性计算客户帐户的总余额,并由getNetworth ()方法,是不执着的。的映射属性映射客户类到crc_customer表;的id的财产customer_id列;和的名字的财产customer_name财产。

的值映射property是一个Groovy闭包对象,它是一种匿名方法。映射闭包的主体是一个方法调用序列,尽管它可能不会立即显现出来。例如,”Id列:'customer _ Id '是对id方法的调用,该方法具有一个map参数,其中包含一个具有专栏:作为键和'customer_id作为值。

映射闭包是DSL(领域特定语言)的一个例子,4这是一种小型语言,用于表示关于领域的信息。Grails使用dsl完成各种配置任务。Groovy应用程序通常也定义一个或多个dsl。Groovy语言的几个特性使编写dsl变得容易,包括闭包、文字列表和映射,以及灵活的语法,例如,不需要在方法参数周围加括号。它们使开发人员能够编写高度可读和简洁的dsl,而不必脱离语言并使用XML等机制。

回到顶部

操作持久对象

应用程序必须保存、加载和删除持久对象。传统ORM框架提供一个API对象,该对象具有操作持久数据的方法。然而,GORM采用了一种非常不同和简单的方法,它利用了Groovy在运行时定义新方法的能力。

在使用传统ORM框架时,应用程序通过调用API对象上的方法来操作持久数据。例如,Hibernate应用程序使用Session对象,它表示到数据库的连接,用于保存、加载和删除持久对象。注意,应用程序通常只需要保存新创建的对象。大多数ORM框架,包括Hibernate,都会跟踪持久性对象的更改并自动更新数据库。

图3一显示演示应用程序如何加载具有指定主键的帐户的代码片段。此代码片段获取当前Session和调用get ()加载指定的帐户。

应用程序的业务逻辑可以使用会话直接。但是,这样做将违反事项分离原则。3.应用程序代码将是业务逻辑和持久性逻辑的混合,这使其更加复杂,测试也更加困难。它还将业务逻辑与ORM框架紧密结合,考虑到Java EE框架发展的迅猛速度,这是不可取的。

更好的方法是使用DAO(数据访问对象)模式,8它将数据访问逻辑封装在一个DAO类中。DAO定义了用于持久化、加载和删除对象的方法。它还定义了查找器方法,这些方法执行查询,稍后将进行更详细的讨论。DAO方法由业务逻辑调用,并调用ORM框架来访问数据库。

图3 b对象的Hibernate DAO示例账户域类。该DAO由AccountDao接口,它定义了公共方法AccountDaoImpl类,该类实现接口并调用Hibernate来访问数据库。

DAO模式简化了业务逻辑,并将其与ORM框架解耦,但它有一些缺点。第一个问题是,许多dao由千篇一律的代码组成,开发和维护起来非常繁琐。这导致一些开发人员放弃了DAO模式,而编写直接调用ORM框架的业务逻辑,尽管这样做有缺点。

减少千篇一律代码数量的一种方法是使用泛型DAO。9它由一个定义CRUD(创建、读取、更新、删除)操作的超接口和一个实现这些操作的超类组成。超接口和超类由实体类参数化,这使得它们具有强类型。应用程序DAO扩展了泛型DAO接口和实现类。使用泛型DAO可以消除部分(但不是全部)千篇一律的代码,因此这只是部分解决方案。

使用dao的另一个问题是,一些应用程序类可能无法引用它们。现代Java EE应用程序使用一种称为依赖注入的机制来解析组件间引用。5当应用程序启动时,汇编程序实例化每个应用程序组件,并向其注入对所需组件的引用。以这种方式解析组件间引用可以简化组件并促进松散耦合。

然而,依赖项注入的一个限制是,它不容易允许域对象等非组件获得对dao等组件的引用。域对象是由应用程序而不是组件组装程序实例化的。这很棘手,虽然不是不可能,13组件汇编程序可以拦截此类对象的实例化并注入依赖项。因此,驻留在域对象中的业务逻辑不能总是引用诸如dao之类的组件。


尽管有局限性,但许多应用程序的开发人员都会发现GORM非常有用。开发人员可以独立于Grails使用GORM,但它针对的是能够从Grails框架的快速开发功能中受益的Web应用程序开发人员。


有几种方法可以绕过这个限制。服务等组件可以使用依赖注入,它们将dao作为方法参数传递给域类,而域类不能使用依赖注入。这在某些情况下工作得很好,但在更复杂的情况下,代码会因为额外的参数而变得混乱。另一个解决方法是将需要使用dao的代码移动到可以使用依赖项注入的组件中。将业务逻辑移出实体的麻烦在于它降低了设计的质量,导致领域模型缺乏活力。

GORM中的动态持久性方法。GORM提供了一种不同风格的持久性API。它没有提供API对象,而是将保存、加载和删除持久对象的方法注入域类。该机制将业务逻辑与底层ORM框架解耦,而不必使用dao。它还消除了应用程序代码获取对ORM框架API对象或dao的引用的需要。

GORM向域类注入了几个方法,包括save (),保存新创建的对象;get (),它根据对象的主键加载对象;而且delete (),用于删除对象。下面是一个使用这些方法的例子:

ins04.gif

此示例创建一个客户对象,并将其保存在数据库中save ().然后通过调用来加载客户Customer.get ().最后,它通过调用删除客户delete ().类的源代码中没有定义这些方法客户类。GORM使用missingMethod () / ExpandoMetaClass机制前面描述过。

GORM动态定义的持久性方法在将应用程序代码与ORM框架解耦时消除了大量DAO代码。GORM回避了非组件如何获得对dao的引用的问题。GORM应用程序中的任何地方的代码都可以执行数据访问操作。当然,这是否总是合适是另一个问题,因为正如我稍后讨论的那样,它可能导致数据库访问代码分散在整个应用程序中。

GORM的一个重要限制是它不支持多个数据库。Hibernate应用程序显式地使用特定的会话,因此可以选择要访问的数据库。GORM应用程序使用注入到域类中的持久性方法,不能选择使用哪个数据库。此外,在撰写本文时,用于配置GORM的机制不支持多个数据库。这种限制可能会阻止许多应用程序使用GORM,包括那些通过使用多个数据库进行水平扩展的应用程序。

回到顶部

执行查询

应用程序可能不知道它需要加载的对象的主键。相反,它必须执行一个基于对象属性值检索对象的查询。当使用传统ORM框架时,应用程序通过调用框架提供的API对象上的方法来执行查询。这些代码通常由dao封装,以将应用程序与ORM框架解耦。与持久性方法一样,GORM采用了一种不同的方法,通常可以简化应用程序代码。

Hibernate提供了几种执行查询的方法。例如,应用程序可以使用查询HQL (Hibernate查询语言)是一种功能强大的面向对象的文本查询语言。图4一是一个DAO查找器,它检索余额小于某个最小值的账户。


GORM在运行时将与持久性相关的方法注入到域类中。它消除了大量的数据访问方法和类,同时仍然将业务逻辑与ORM框架分离。


此方法获得一个会话并创建一个查询对象。然后设置查询的参数并执行查询,该查询将返回一个列表账户对象。

Hibernate应用程序还可以使用Criteria Query API执行查询。这个API提供了以编程方式构建查询的方法。当应用程序需要动态构建查询时,它特别有用,因为它消除了连接查询字符串片段的需要。(图4 b是查找低余额账户的标准查询的一个示例。)此代码段创建一个标准对象的账户类。然后,它添加一个限制并执行查询。

DAO查找器的一个问题是,大多数都具有与示例相同的结构:创建查询、设置参数和执行查询。唯一的变量是查询和参数。与持久化方法一样,这些千篇一律的方法和包含它们的dao的开发、测试和维护非常繁琐。

回到顶部

动态GORM查找器

GORM有一个动态查找器机制,不需要编写简单的查询和DAO查找器方法。它使用Groovy的动态功能向域类添加查找器方法。例如,应用程序可以找到余额较低的帐户,如图5一个.如果方法名遵循某些命名约定,则missingMethod () / ExpandoMetaClass机制拦截对方法的调用并定义一个方法,该方法解析方法名以构建查询并执行查询。

GORM动态查找器支持丰富的查询语言。查找器方法名称可以使用比较操作符,例如等于、小于和大于。他们也可以使用而且,逻辑运算符。尽管查询语言仅限于单个类的属性——没有连接——但许多查询可以表示为动态查找器。GORM应用程序包含的数据访问代码少得多,对Hibernate框架的显式依赖也少得多。此外,由于查找器方法在域类上很容易获得,GORM避免了需要解析组件间引用的问题。

这些查找器方法的一个潜在缺点是,方法名是查询的定义。为封装实际实现的查询定义有意的显示名称并不总是可能的。因此,不断变化的业务需求可能导致查找程序方法的名称发生变化,从而增加了维护应用程序的成本。

对于需要执行更复杂查询的应用程序,GORM提供了两个不同的选项。应用程序可以直接执行HQL查询。例如,应用程序可以执行HQL查询来检索余额较低的帐户,如图5 b.此代码段调用findAll ()方法,GORM将该方法注入到每个域类中。它接受一个HQL查询和一个参数列表作为参数。

这个API的一个很好的特性是,它允许应用程序执行HQL查询,而不显式调用Hibernate API。应用程序不需要解决获取对DAO或其他组件的引用的问题。然而,有一个缺点是HQL的知识是硬连接到应用程序中的。

另一种选择(在动态构造查询时特别有用)是使用GORM条件查询,它包装了前面描述的Hibernate criteria API。与其他api一样,GORM动态地注入一个createCriteria ()方法放入域类中。此方法允许应用程序在不显式依赖Hibernate API的情况下构造和执行查询。

图5 c是检索低余额账户的查询的GORM条件查询版本。的createCriteria ()方法返回用于构建查询的对象。应用程序通过调用执行查询列表(),它接受一个Groovy闭包作为参数,并返回匹配对象的列表。闭包参数包含方法调用,例如lt ()它们向查询添加了限制。

应用程序可以使用这些api执行动态查找器不支持的查询。一个潜在的缺点(可以被认为是GORM的一个弱点)是可能缺乏模块化和违反关注点分离原则。存在将域类的数据访问操作分散到整个应用程序的风险。一些数据访问方法是由域类定义的,但其余的方法混杂在应用程序的业务逻辑中,这可能被认为是缺乏模块化。理想情况下,这样的数据访问逻辑应该封装在dao中,但不幸的是,GORM不显式地支持它们。

回到顶部

总结

GORM提供了一种创新风格的O/R映射,简化了应用程序代码。实现这一点的关键方法之一是利用Groovy语言的动态特性。GORM在运行时将与持久性相关的方法注入到域类中。它消除了大量的数据访问方法和类,同时仍然将业务逻辑与ORM框架分离。

GORM对CoC的广泛使用简化了应用程序代码。只要GORM对表和列名的默认值与模式匹配,就可以将类映射到数据库模式,而无需进行很少配置或根本不需要配置。GORM还向每个域类注入主键和版本号字段,这进一步减少了所需的编码量。

GORM有一些局限性。它不容易支持多个数据库。动态查找器方法不能具有封装查询的有意显示名称。GORM缺乏对DAO类的支持,尽管复杂的应用程序可能会从它们提供的改进的模块化中受益。使用遗留模式的应用程序将不能利用CoC,因为它们需要显式配置ORM。

尽管有这些限制,许多应用程序的开发人员会发现GORM非常有用。开发人员可以独立于Grails使用GORM,但它针对的是能够从Grails框架的快速开发功能中受益的Web应用程序开发人员。此外,在开发访问单个数据库的应用程序或使用数据库中间件使多个数据库显示为单个数据库时,最好使用GORM。当开发人员能够控制数据库模式并利用GORM的CoC特性时,他们将从GORM中获得最大的好处。

回到顶部

致谢

我要感谢以下审稿人对本文草案的有益反馈:Ajay Govindarajan, Azad Bolour, Dmitriy Volk, Brad Neighbors和Scott Davis。我还要感谢SF Bay Groovy和Grails聚会的成员和匿名者ACM队列为本文提供反馈的审阅人员。

回到顶部

参考文献

1.Apache OpenJPA;http://openjpa.apache.org/。

2.鲍尔,C,加文,K,使用Hibernate的Java持久性.曼宁出版社,2006年。

3.Dijkstra算法。论科学思想的作用。在计算机写作选集:个人视角。斯普林格-弗拉格,1982,6066。

4.特定于域的语言;www.martinfowler.com/bliki/DomainSpecificLanguage.html。

5.控制容器的反转与依赖注入模式;www.martinfowler.com/articles/injection.html(2004)。

6.Java SE桌面技术;http://java.sun.com/javase/technologies/desktop/javabeans/index.jsp。

7.科尼格(D.)、格洛弗(A.)、金(P.)、拉弗格(G.)和斯基特(J.)。Groovy的应用曼宁出版社,2007年。

8.Marinescu F。EJB设计模式:高级模式、过程和习语曼宁出版社,2007年。

9.melqvist, P.不要重复DAO!2006;www.ibm.com/developerworks/java/library/j-genericdao.html。

10.NHibernate;http://sourceforge.net/projects/nhibernate/。

11.甲骨文TopLink;www.oracle.com/technology/products/ias/toplink/index.html。

12.巧克力,G。Grails的权威指南, Apress, 2006。

13.Spring框架参考文档。看到(©可配置;http://static.springframework.org/spring/docs/2.5.x/reference/index.html。

14.托马斯,D,福勒,C,亨特,A。编程Ruby:实用的程序员指南.实用主义书架,2004年。

15.托马斯,D.,汉森,D.,布里德,L.,克拉克,M.,福克斯,T.和施瓦茨,A.。使用Rails的敏捷Web开发。实用主义书架,2005年。

16.沃尔斯,C;布雷登巴赫,R。Spring in Action,第二版。曼宁出版社,2007年。

回到顶部

作者

克里斯·理查森是一个拥有20多年经验的开发人员和架构师。他是pojo在运行(Manning Publications, 2006),该书描述了如何使用pojo和轻量级框架构建企业Java应用程序。他经营着一家咨询和培训公司,专门帮助公司降低开发成本,提高开发人员的效率。他曾在Insignia、BEA和其他公司担任技术领导。Richardson是Cloud Tools和Cloud Foundry的创始人,Cloud Tools是一个在Amazon EC2上部署Java应用程序的源项目,Cloud Foundry是一家为云上Java应用程序提供外包数据中心管理的初创公司。他拥有英国剑桥大学(University of Cambridge)的计算机科学学位,现居住在加利福尼亚州奥克兰(Oakland);www.chrisrichardson.net。

回到顶部

脚注

本文的以前版本出现在ACM队列, 2008年5 / 6月。

DOI: http://doi.acm.org/10.1145/1498765.1498783

回到顶部

数据

F1图1所示。

F2图2。

F3图3。

F4图4。

F5图5。

回到顶部


©2009 acm 0001-0782/09/0400 $5.00

允许为个人或课堂使用本作品的全部或部分制作数字或硬拷贝,但不得为盈利或商业利益而复制或分发,且副本在首页上附有本通知和完整的引用。以其他方式复制、重新发布、在服务器上发布或重新分发到列表,需要事先获得特定的许可和/或付费。

数字图书馆是由计算机协会出版的。版权所有©2009 ACM, Inc.


没有找到条目

Baidu
map