领先一步
VMware 提供培训和认证,助您加速进步。
了解更多距离上次的“倒计时”博客文章已经有一段时间了,但 2.0.0.RC3 的发布给了我一个很好的理由来再写一篇。在上一篇文章中,我重点介绍了数据库迁移以及我们如何标准化新的数据库迁移插件。我将在这里继续讨论持久化主题,并介绍几个很棒的新功能,特别是在查询方面。
让我们从一些次要的改进开始。首先,抽象域类现在按照大多数人的预期进行处理:抽象基域类会为其自身及其子类生成一个表。例如,考虑图中所示的层次结构。在 Grails 2 之前,这将导致独立的“employee”和“manager”表。现在您只会得到一个“person”表。
不幸的是,这构成了一个重大更改,但新的行为更有意义,因此我们认为在这种情况下,这种更改是合理的。如果您目前正在使用抽象域类,那么您可以在升级时:(1) 将它们移到“src/groovy”目录中;或者 (2) 迁移您的数据,例如使用数据库迁移插件。
下一个特性简化了一个常见的编码模式,这在应用程序引导程序中特别有用。您是否曾发现自己编写过以下代码?
def adminRole = Role.findByName("admin")
if (!adminRole) {
adminRole = new Role(name: "admin").save()
}
它基本上是“我需要一个特定的对象,但如果它不存在就创建它。”它常见且足够冗长,以至于需要一个专门的方法。事实上,现在有四种方法专门用于此模式。它们是
前两个是动态的变体findBy*()。后两个接受一个包含域类属性及其值的参数映射。每组也有“Create”和“Save”版本,前者表示如果域实例不存在则应创建但不保存。后者当然会自动保存新实例。
那么,这些方法在我们的管理员角色示例中是怎样的呢?
我想您会同意,这更加简洁和信息丰富。
在介绍重要功能(也就是我最喜欢的功能)之前,我只想指出 Burt Beckwith 的 Datasources 插件现在已并入 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()。除此之外,这两种语法是相同的。
上面的例子还展示了投影支持:能够检索单个属性的值或聚合值,而不是域实例本身。因此,上面的代码将返回一个BookID 列表,而不是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 类,顺便说一句)!
我将在一个截屏视频中演示这一点,该视频将与下一篇 Countdown to Grails 2.0 博客文章一起发布,但请随意自己尝试。您可以添加、删除和重命名属性。您甚至可以更改它们的类型。您唯一需要注意的是,当域类重新加载时,dbCreate设置生效。如果将其保留为默认的“create-drop”,那么数据库中任何现有数据都将丢失。但是,如果您将值更改为“update”,则不会丢失任何数据,并且数据库架构仍会更新。唯一需要注意的是,“update”不能很好地处理某些类型的迁移,但在开发过程中这通常不是问题。
Grails 2 版本为每个人都带来了一些东西。对于日常开发,Where 查询和可靠的域类重载将使生活更轻松。对多个数据源的支持将令需要从单个应用程序处理多个关系数据库的开发人员满意。而数据库迁移插件则标准化了处理关系数据库生产数据迁移的方法。所有这些都将很快与您见面!