更多 Grails 1.3 特性

工程 | Peter Ledbrook | 2010年5月24日 | ...

上周,我描述了 Grails 如何将插件视为普通的依赖项,可以从 Maven 兼容的仓库中拉取。尽管这是 1.3 版本的主要新特性,但并非唯一。在本文中,我将介绍其他一些特性,首先是我最近才发现的一个特性。

命名查询

GORM 提供了三种执行数据库查询的方式
  • 动态查找器,例如 Book.findByTitleAndAuthorLike(...);
  • Criteria 查询,它使用优雅的 DSL;以及
  • HQL,Hibernate 类似 SQL 的查询语言。
这三种特性提供了易用性和强大功能的有效结合,为您提供了所需的灵活性。然而,仍然缺少一些东西。

开发一个非平凡的 Grails 应用,您很快就会意识到经常需要反复使用相同的查询。您该怎么办?复制粘贴技术很简单,但会导致主要的维护问题。您可以为每个通用查询编写服务方法,但这会导致服务粒度过细,并且领域模型变得相当愚蠢。这些查询的理想位置是在领域类本身上。

这就是命名查询的作用所在——一个在 Grails 1.2 中悄然出现的特性。

一个例子

让我们考虑一个与报告相关的简单领域模型:[caption id="attachment_4791" align="alignnone" width="398"]Domain model for reports[/caption]

基本思想是,一份报告可以关于一台或多台服务器,并且每月可能生成一次或多次。所以dow代表“星期几”(day of week),而wom代表“月度周次”(week of month)。每台服务器都有一个关联的位置,在这个大大简化的模型中,它仅仅是一个城市名称。

现在考虑一下应用程序可能想从这个模型中提取什么样的信息:也许是所有在月度第一周生成的报告,或者所有关于特定服务器的报告。我们可以向Report类添加静态方法来提供此类查询,但命名查询为我们提供了一些额外的优势,稍后您将看到。

正如您对 Grails 的预期一样,创建命名查询非常简单。只需向相关的领域类添加一个静态的namedQueries属性,并为其分配一个闭包

class Report {
    String name

    static hasMany = [frequencies: Frequency, servers: Server]

    static namedQueries = {
        inFirstWeek {
            frequencies {
                eq("wom", 1)
            }
        }

        inWeek { wom ->
            frequencies {
                eq("wom", wom)
            }
        }

        dilbertsReports {
            servers {
                eq("mgrEmail", "[email protected]")
            }
        }

        inCity { city ->
            servers {
                location {
                    eq("city", city)
                }
            }
        }
}

上面的代码设置了四个查询:inFirstWeek、inWeek、dilbertsReports 和 inCity。然后,您可以在可以使用动态查找器的地方使用它们,例如在控制器动作或服务方法中。如果您想检索所有在月度第一周生成的报告,请像这样调用相关的命名查询

Report.inFirstWeek.list()

如果您想获取所有在月度其他周生成的报告,请使用inWeek代替

Report.inWeek(2).list()

看到如何向命名查询传递参数了吗?只需确保您的命名查询闭包声明了适当数量的参数。

希望您能看到声明和使用命名查询是多么容易,但在继续之前,有几点需要澄清。

首先,您必须使用 Grails 的 criteria DSL 编写查询。如果您一直推迟学习 criteria DSL,那么现在您有了充分的理由停止拖延!

其次,您将 DSL 调用为静态属性(如果您不向其传递任何参数)或方法,然后是标准的 GORM 检索方法,例如list(), get()或动态查找器。这意味着您可以向命名查询添加额外的过滤。还需要指出的是,get()只有在命名查询结果包含所需实体的情况下,才会返回领域实例。否则,get()将简单地返回null.

换句话说,假设inFirstWeek查询返回 ID 为 1、3 和 6 的领域实例。那么

Report.inFirstWeek.get(3)

将返回 ID 为 3 的领域实例,而

Report.inFirstWeek.get(2)

将返回null,即使Report.get(2)返回一个真实的领域实例。因此,命名查询就像一个过滤器。

到目前为止,一切顺利。命名查询与get(), list()和动态查找器的结合方式可能已经足以成为您立即使用它们的理由。但 Grails 1.3 还藏着另一个绝招。

链式查询

任意组合命名查询听起来如何?通过链式命名查询,您就可以做到这一点。例如,如果我想获取 Dilbert 在月度第一周生成的所有报告,我可以调用
Report.dilbertsReports.inFirstWeek.list()

或者,如果我想获取所有关于伦敦服务器的第一周报告,我可以使用

Report.inFirstWeek.inCity("London").list()

实际上,您可以链式调用任意数量的命名查询,只要它们都返回相同类型的领域类。

命名查询提供了一种强大的查询重用技术,实现和使用都非常简单。现在您可以拥有一个非常丰富的领域模型,并且客户端代码易于阅读和理解。这有多棒?

现在我想快速看一下其他 Grails 1.3 特性。

其余亮点

一些虽小但仍然有用的特性也进入了 Grails 1.3 版本。其中最重要的是升级到了 Groovy 1.7(Grails 1.2 及之前版本基于 Groovy 1.6)。

Groovy 1.7

Groovy 1.7 版本进行了许多修复和增强,但对于 Grails 开发者来说,也许有两个最为重要
  1. 现在支持匿名类和内部类 - 因此与 Wicket 等框架的集成应该容易得多。
  2. Power 断言 - 现在您可以使用 Groovy 的assert关键字,而不是 JUnit/TestNG 的替代方案,以获取关于断言失败原因的令人印象深刻的诊断信息。这是我最喜欢的新特性!

脏检查

不,这与家务无关!正如许多人所知,Hibernate 会自动检查领域实例是否已修改,并在会话结束时持久化这些更改。GORM 现在允许您通过isDirty()方法访问此特性
def book = Book.get(10)
assert !book.dirty

book.title = "Unknown"
assert book.dirty
assert book.isDirty("title")
assert !book.isDirty("author")

看到如何检查单个字段是否已修改了吗?

全局布局

您可能知道,Grails 允许您通过显式方式(例如通过<meta>标签)或通过约定来将布局应用于视图。它之前不允许您指定视图的默认布局作为备用选项。现在这个缺点已经纠正了,您可以通过 Config.groovy 中的设置来指定默认布局
grails.sitemesh.default.layout = 'defaultLayout'

或者创建文件grails-app/views/layouts/application.gsp。第一种方法会从grails-app/views/layouts/defaultLayout.gsp.

获取布局

JUnit 4

对于所有测试爱好者来说,Grails 终于默认捆绑了 JUnit 4,所以您现在可以随心所欲地注解您的测试用例了。至此,我将结束关于 Grails 1.3 特性的这一部分。希望您能好好利用命名查询!下次,我将介绍 in-place 插件。

获取 Spring 新闻通讯

订阅 Spring 新闻通讯,保持联系

订阅

领先一步

VMware 提供培训和认证,助力您快速进步。

了解更多

获取支持

Tanzu Spring 在一份简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举办的活动

查看 Spring 社区即将举办的所有活动。

查看全部