提升技能
VMware 提供培训和认证,助力您快速提升。
了解更多自上一篇“倒计时”博客文章以来已经过去了一段时间,但 2.0.0.RC3 的发布给了我一个很好的理由再写一篇。在上一篇文章中,我重点讨论了数据库迁移以及我们如何将新的 Database Migration Plugin 作为标准。在本文中,我将继续讨论持久化这一主题,并介绍一些很棒的新功能,特别是在查询方面。
让我们从一些小的改进开始。首先,抽象域类现在按照大多数人期望的方式处理:一个抽象基域类会为其自身及其子类生成一个表。例如,考虑图中所示的层次结构。在 Grails 2 之前,这会生成独立的“employee”和“manager”表。现在您只会得到一个“person”表。
不幸的是,这构成了一个破坏性更改,但新的行为更加合理,因此我们认为在这种情况下这种改变是合理的。如果您目前正在使用抽象域类,那么在升级时您可以选择:(1) 将它们移动到 'src/groovy' 目录中;或者 (2) 迁移您的数据,例如使用 Database Migration plugin。
下一个功能简化了一个常见的编码模式,这在应用程序启动时特别有用。您是否曾发现自己编写了类似下面的代码?
def adminRole = Role.findByName("admin")
if (!adminRole) {
adminRole = new Role(name: "admin").save()
}
这基本上是“我需要一个特定的对象,如果它不存在就创建一个”。这种模式很常见且代码量较大,足以证明应该为其提供专门的方法。事实上,现在有四个专门针对这种模式的方法。它们是
前两个是动态findBy*()。后两个接受包含域类属性及其值的参数映射。每组都有“创建”(Create)和“保存”(Save)版本,前者表示如果域实例不存在,应该创建它,但不保存。后者当然会自动保存新实例。
那么,这些方法在我们的管理员角色示例中看起来是什么样的呢?
我想您会同意,这要简洁和信息量大得多。
在介绍主要功能(也就是我最喜欢的功能)之前,我想先指出一点,Burt Beckwith 的 Datasources plugin 现在已被合并到 Grails 核心中。这意味着一旦您下载 Grails,就可以开始定义多个(关系型)数据源。然后您可以做一些有趣的事情,例如将一个域类映射到一个特定的数据库
class User {
...
static mapping = {
datasource 'auth'
}
}
甚至可以指定用于单个检索操作的数据源
// Retrieve the zip code specifically from our audit DB
def zipCode = ZipCode.auditing.get(42)
和存储操作
// Save some zip code to the audit DB
zipCode.auditing.save()
《用户指南》从用户角度详细介绍了它的工作原理。
现在进入主要内容:新的 Where 查询。Grails 在查询方面并不缺乏选择,支持动态查找器、条件查询和 HQL,因此您可能会想,为什么我们还要引入另一种查询方式。嗯,条件查询功能强大,但语法可能令人困惑。HQL 通常用于更高级的用法。动态查找器仅适用于最简单的场景,并且不允许您查询关联关系。Where 查询通过引入一种对程序员来说更自然的语法,同时提供与条件查询几乎一样强大的功能,弥合了动态查找器和条件查询/SQL 之间的差距。
让我们来看一个简单的例子
def year2000 = ...
Book.where { author =~ "Stephen%" && publishDate > year2000 }.list()
这个查询展示了几个强大的功能,但首先吸引我的是它的可读性。作为 Groovy 或 Java 开发者,我能认出条件中使用的运算符及其组合方式。我可以很快地知道上面的查询会返回作者姓名以“Stephen”开头且出版日期在 2000 年之后的书籍。即使没有阅读任何关于 Where 查询语法的文档!
除了上述查询的可读性之外,请注意用于条件的运算符(=~ 映射到 SQL 中的 'ilike')、用于组合条件的逻辑运算符(&& 和 ||)以及list()实际执行查询的 方法。这些逻辑运算符对我来说真是太方便了,因为我发现它们比条件查询中的 'and' 和 'or' 块更容易阅读和理解。
的重要性在于list()方法是DomainClass.where()返回所谓的“分离式查询”,这意味着您可以一次又一次地重用它。您甚至可以使用动态查找器来代替list()以进一步过滤结果。事实上,也可以使用标准条件查询语法获得分离式查询——《用户指南》对此有更多信息。
由于 Where 查询接受一个闭包,您可以做更高级的事情,例如在条件中包含 Groovy 条件
def constrainName = true
def bookIds = Book.where {
if (constrainName) {
author.name =~ "Stephen%"
}
publishDate in start..end
}.property("id")
这意味着您可以轻松地包含或排除条件。一个可能想到的问题是,两个条件是如何组合的?默认情况下,独立语句中的条件,例如publishDate和author.name在此示例中,通过隐式 AND 组合。如果您想使用 OR 组合这些语句,则可以使用新的whereAny()方法来代替where()。除此以外,两者语法相同。
上面的例子还演示了投影支持:能够只检索单个属性的值或聚合值,而不是域实例本身。因此,上面的代码将返回一个列表,包含Book的 ID,而不是Book实例。您甚至可以链式调用投影以获得额外的灵活性。
最后,我想强调一个很酷的功能:在查询条件中包含聚合函数非常容易。这个例子返回所有年龄大于平均年龄的作者
Author.where { age > avg(age) }
事实上,这只是子查询冰山一角,您还可以对子查询应用额外的条件,如此示例所示
def query = Person.where {
age > avg(age).of { lastName == "Simpson" } && firstName == "Homer"
}
如您所见,Where 查询在一个易于使用的包中提供了大量功能。我真诚地认为它们将成为 Grails 应用程序中的默认查询方式,取代条件查询,并有可能取代动态查找器。
如果 Where 查询还不够吸引人,那么最后一个功能将真正吸引 Grails 的长期用户:健壮的域类热加载,通过run-app!在 Grails 的早期版本中,更改域类会导致 Servlet 容器重启,这可能需要一些时间。现在,修改域类就像修改任何其他类一样(包括位于src/java目录中的 Java 类)!
我将在随下一篇 Grails 2.0 倒计时博客文章发布的截屏视频中演示这一点,不过您也可以自己尝试一下。您可以添加、删除和重命名属性。您甚至可以更改它们的类型。您唯一需要注意的是,当域类热加载时,dbCreate设置会生效。如果保留默认值“create-drop”,那么数据库中任何现有数据都将丢失。但是,如果您将值更改为“update”,则不会丢失任何数据,并且数据库模式仍会更新。唯一需要注意的是,“update”对于某些类型的迁移处理得不是很好,但在开发过程中这通常不是问题。
Grails 的第 2 版本为每个人都带来了一些新东西。对于日常开发,Where 查询和健壮的域类热加载将使工作更轻松。对多数据源的支持将令那些需要从单个应用程序访问多个关系型数据库的开发者感到高兴。数据库迁移插件则规范了关系型数据库生产环境数据迁移的处理方法。所有这些都将很快到来!