领先一步
VMware 提供培训和认证,助你飞速发展。
了解更多Grails 众多优秀特性之一就是它能根据你的领域模型自动创建数据库 schema。诚然,这是 Grails 使用的 Hibernate 的一个特性,但这仍然能帮助你快速启动数据库驱动的 Web 应用,而无需担心数据库 schema 的问题。
当你的应用部署到生产环境后会发生什么?在开发过程中,服务器运行之间丢失数据不是大问题。但在生产环境中,你不能直接删除数据库。因此,数据库数据源设置的“create”和“create-drop”值就被排除了。那“update”呢?它不会清除数据库中的数据,所以经常被大家使用。然而,它在生产环境中效果不佳,因为它存在明显的限制。例如,它无法处理简单的列重命名,当然也无法处理对现有数据的修改,而这些修改可能是升级的必要部分。尽管在生产部署中使用dbCreate设置为“update”很有诱惑力,但这通常是错误的解决方案。dbCreate = "update"那该怎么办呢?使用 SQL 脚本来执行迁移?这当然可行,但为给定的 GORM 领域模型变更创建适当的 SQL 脚本并不容易。如果你需要支持多种数据库类型,那这种方法也不合适,因为每种数据库的 SQL 可能会不同。
所剩的选择是什么?用 SQL 脚本来执行迁移?这当然可以,但要为你的 GORM 领域模型中的给定变更创建相应的 SQL 并不容易。而且如果你需要支持多种数据库类型,这也不合适,因为每种数据库的 SQL 可能都不同。
幸运的是,有一个灵活的、与数据库无关的工具用于执行 schema 迁移:Liquibase。更好的是,有两个 Grails 插件可以比直接使用 Liquibase 更方便:Liquibase 插件和 Autobase。
那么,既然这些插件已经存在很长时间了,Grails 2.0 有什么新变化呢?数据库迁移是 Grails 用于任何严肃工作的重要组成部分,因此 Grails 团队决定应该有一个官方的方式来处理它们。结果是出现了一个新插件,它结合了 Liquibase 和 Autobase 插件的最佳部分:数据库迁移插件。
尽管核心数据库迁移支持是 Grails 2.0 路线图的一部分,但没有理由将其绑定到该版本。这意味着你也可以在 Grails 1.3 项目上使用该插件。该插件为你的应用带来了这些特性:
它提供了 20 多个命令,给你充足的控制权。你可以从插件的用户指南中了解更多信息,但简单来说,它为管理 Grails 应用新版本的数据库受控升级提供了极大的帮助。如果你愿意,仍然可以使用其他插件甚至完全不同的方法,但对大多数用户来说,这绝对是推荐的方式。
本文的其余部分将向你展示该插件的一个常见使用模式。
想象一下,你一直在努力开发一个 Grails 应用,现在你想将第一个版本部署到生产环境。是时候考虑如何管理数据库升级了。此时,生产数据库甚至还没有创建。因此,将数据库迁移插件声明为运行时依赖项,如下所示:
grails.project.dependency.resolution = {
inherits "global"
...
plugins {
runtime ":database-migration:1.0"
compile ":hibernate:$grailsVersion"
compile ":jquery:1.6.1.1"
compile ":resources:1.0.2"
build ":tomcat:$grailsVersion"
}
...
}
然后运行grails compile。在撰写本文时,1.0 是插件的最新版本。请查看 Grails 插件门户以了解任何时候的最新版本。
插件可用后,你就可以开始数据库迁移之旅了。正如我所说,生产数据库尚未创建。你可以部署应用的初始版本,使用dbCreate设置为“update”,这也能正常工作。但出于我稍后讨论的原因,我希望鼓励你从数据库迁移 changelog(即迁移脚本)初始化数据库。别担心,这比你想象的要省事得多。
值为“update”并且运行良好。但由于我稍后会讨论的原因,我希望鼓励你从数据库迁移 changelog(即迁移脚本)来初始化数据库。别担心,这比你想象的要省事得多。dbCreate设置移除。然后运行
grails dbm-create-changelog grails prod dbm-generate-gorm-changelog --add changelog-1.0.groovy
(现在是尝试新的 Grails 2 交互模式的好时机!)
上述命令将创建一个grails-app/migrations/changelog.groovy文件,它将成为你的父 changelog 文件。第二个命令然后生成一个迁移脚本,grails-app/migrations/changelog-1.0.groovy,该脚本将针对空数据库创建与当前应用版本对应的 schema。父 changelog 也会更新以包含这个新的 changelog。请注意,你的数据库将保持为空!
虽然 Liquibase 旨在与数据库无关,但最好在配置了适当类型数据源(通常是“production”)的 Grails 环境中运行各种生成和 diff 命令。这将确保你无需对生成的 changelog 进行太多更改。
[提示 标题=Changelog 名称] 插件没有强制要求你对 changelog 文件名采用任何特定的命名约定。在本文中,我只是使用了 'changelog-'
我们为什么要创建 1.0 的 changelog 而不是使用“update”dbCreate设置呢?最初的 changelog 意味着你可以将你的应用部署到一个全新的、空的数据库。所有迁移都将按预期工作,因为它们将从一个已知、固定的 schema(即一个空的 schema)按顺序运行。“update”设置的问题在于,它总是会创建一个与当前领域模型匹配的 schema。较旧的 changelog 根本无法运行,因为数据库的状态不是它们所期望的!如果你愿意,你的第一个应用版本仍然可以使用“update”,但上述方法意味着你可以在早期测试你的初始 changelog。
此时,值得回顾一下changelog-1.0.groovy文件,看看迁移是什么样子,以及是否有需要更改的地方。你可能会看到添加了比实际需要更多的索引和约束,因此可能需要进行一些删减。
好的,我们现在有一个迁移脚本了。但是当我们运行grails prod run-app时,应用不会启动:数据库仍然是空的。为什么?迁移脚本不会在启动时自动运行,这意味着你必须手动使用dbm-update这样的命令更新数据库,或者在启动时启用迁移执行。我更喜欢后者,特别是在许多情况下你无法从 Grails 命令行访问生产数据库。因此,将以下设置添加到Config.groovy文件中:
grails.plugin.databasemigration.updateOnStart = true
grails.plugin.databasemigration.updateOnStartFileNames = ["changelog.groovy"]
现在当你启动应用时,迁移脚本将会运行,你的应用也将工作!当你重启服务器时,插件会检测到迁移已经运行过,所以它们会被忽略。
数据库迁移的全部意义在于,随着应用的演变,你会对数据库进行变更并控制这个过程。所以现在想象一下,你已经对应用做了一些工作,并想发布一个新版本,比如 1.0.1。要升级生产数据库,你需要为你的领域模型变更创建一个 changelog。你可以手动完成,但利用数据库迁移插件的“diff”命令可以省下不少力气。
要执行 diff,你需要一个插件可以用来与当前领域模型进行比较的数据库。同样,使用与生产环境相同类型的数据库是个好主意。此外,数据库必须处于其原始状态,即在当前领域模型变更发生之前。换句话说,不要对数据库运行dbCreate = "update"启用的应用程序!事实上,为每个应用版本创建数据库转储是值得的,这样万一你不小心以这种方式更新了数据库,还可以回滚。
好了,警告到此为止。让我们创建下一个 changelog:
grails prod dbm-gorm-diff --add changelog-1.0.1.groovy
和之前一样,这将创建一个changelog-1.0.1.groovy文件在 migrations 目录下,并从父 changelog 中包含它。你还需要检查生成的 changelog 并可能进行调整,但编辑现有文件比从头创建一个新文件要容易得多。就这样!现在你可以将这个 changelog 和相应的领域类变更一起提交到版本控制。事实上,我建议你总是将领域类变更和相应的迁移脚本修改放在同一个提交中。
changelog 文件本身看起来像这样:
databaseChangeLog = {
changeSet(id: "UpdateDescriminatorForPluginTabs", author: "pledbrook") {
update(tableName: "content") {
column name: "class", value: "org.grails.plugin.PluginTab"
where "title like 'description-%' and class = 'org.grails.wiki.WikiPage'"
}
}
changeSet(id: "IssuesUrlForPlugins", author: "pledbrook") {
addColumn(tableName: "plugin") {
column name: "issues_url", type: "varchar(255)", {
constraints nullable: true
}
}
}
}
如你所见,它们只是变更集的集合,每个变更集包含一些数据库重构。每个变更集在每个 changelog 中都需要一个作者唯一的 ID,以便 Liquibase 可以跟踪它是否已应用(Liquibase 跟踪的是变更集,而不是 changelog!)。上面的例子展示了如何更新现有数据(ID 为 "UpdateDescriminatorForPluginTabs")以及添加一个新列。其他支持的重构包括:
完整的重构范围在 Liquibase 手册中有所描述,尽管所有示例都是 XML。幸运的是,XML -> Groovy 的映射相当直接:
最后,你可能想知道如何组织你的 changelog。是每个重构一个变更集?还是每个 changelog 一个变更集?或者介于两者之间?这取决于你,但每个源码控制提交对应一个变更集效果不错。换句话说,对于包含领域模型变更的每个提交,你创建一个变更集。或者,你可能希望每个数据库表对应一个变更集。做你认为合适的方式。
本文到此为止。正如你所见,适当的数据库迁移支持对于任何生产数据库支持的 Web 应用都是重要的一部分,因此我们现在拥有一个官方支持且功能强大的插件来满足这一需求是个好消息。它仅适用于关系型数据库(所以对于 Redis、MongoDB 等,恐怕没有迁移支持),但它应该仍然能够满足绝大多数 Grails 用户的需求。快去试试吧!