Grails 2.0 倒計時:資料庫遷移

工程 | Peter Ledbrook | 2011年8月17日 | ...

Grails 的眾多出色功能之一是它能夠根據您的領域模型自動為您建立資料庫 schema。誠然,這是 Grails 使用的 Hibernate 的一個功能,但它仍然可以幫助您非常快速地開始使用資料庫驅動的 Web 應用程式,而無需擔心資料庫 schema。

一旦您的應用程式投入生產,會發生什麼?在開發過程中,伺服器執行之間丟失資料並不是一個大問題。但是您不能直接在生產環境中刪除資料庫。因此,這排除了dbCreate資料來源設定。“更新”怎麼樣?它不會清除資料庫中的資料,因此人們經常使用它。然而,它不適合生產環境,因為它有顯著的侷限性。例如,它無法處理簡單的列重新命名,當然也無法處理現有資料的修改,而這可能是升級的必要部分。儘管使用dbCreate = "update"來部署到生產環境很有誘惑力,但這通常是錯誤的解決方案。

那剩下什麼選擇呢?用於執行遷移的 SQL 指令碼?這當然可行,但要為 GORM 領域模型中的給定更改建立適當的 SQL 並不容易。而且,如果您需要支援多種資料庫型別,這也不適用,因為每種資料庫的 SQL 可能都會有所不同。

幸運的是,有一個靈活的、與資料庫無關的工具可以執行 schema 遷移:Liquibase。更好的是,有兩個 Grails 外掛可以使 Liquibase 的使用比平時更容易一些:Liquibase 外掛Autobase

那麼,如果這些外掛已經存在很長時間了,Grails 2.0 有什麼新功能呢?資料庫遷移是任何嚴肅工作中使用 Grails 的重要組成部分,因此 Grails 團隊決定應該有一種官方方式來處理它們。結果是一個新外掛,它結合了 Liquibase 和 Autobase 外掛的最佳部分:資料庫遷移外掛

簡而言之

儘管核心資料庫遷移支援是 Grails 2.0 路線圖的一部分,但沒有理由將其繫結到該版本。這意味著您也可以在 Grails 1.3 專案中使用該外掛。該外掛為您的應用程式帶來了以下功能

  • 宣告式資料庫 schema 和資料遷移
  • Groovy 和 XML 遷移指令碼
  • 手動或自動執行遷移
  • 自動跟蹤已應用的遷移
  • 透過比較領域模型和資料庫生成遷移

它提供了超過 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”值部署應用程式的初始版本,這會很好地工作。但出於我稍後將討論的原因,我想鼓勵您從資料庫遷移變更日誌(即遷移指令碼)初始化資料庫。別擔心,這比您想象的要省力得多。

訣竅是使用外掛的命令之一為我們生成遷移指令碼。首先,您需要確保您的“prod”資料庫為空,並刪除該環境的任何dbCreate設定。然後執行

grails dbm-create-changelog
grails prod dbm-generate-gorm-changelog --add changelog-1.0.groovy

(現在是嘗試新的 Grails 2 互動模式的好時機!)

上述操作將建立一個grails-app/migrations/changelog.groovy檔案,它將成為您的父變更日誌檔案。第二個命令然後生成一個遷移指令碼,grails-app/migrations/changelog-1.0.groovy,它將採用一個空資料庫併為當前版本的應用程式建立適當的 schema。父變更日誌也已更新,以包含這個新指令碼。請注意,您的資料庫將保持為空!

儘管 Liquibase 旨在實現資料庫無關性,但最好在配置了適當型別資料來源(通常是“production”)的 Grails 環境中執行各種生成和差異命令。這將確保您無需對生成的變更日誌進行太多更改。

[標註標題=變更日誌名稱] 外掛不會對變更日誌檔名強制執行任何特定的命名約定。在本文中,我只是使用“changelog-”,因此每個變更日誌都與應用程式的特定版本相關聯。它運作良好。[/標註]

我們為什麼要建立 1.0 變更日誌,而不是使用“update”dbCreate設定?初始變更日誌意味著您可以將應用程式部署到全新的空資料庫。所有遷移都將按預期工作,因為它們將從已知、固定的 schema(即空 schema)按順序執行。“update”設定的問題在於,它總是會建立一個與當前領域模型匹配的 schema。舊的變更日誌根本不會執行,因為資料庫不處於它們預期的狀態!如果您願意,仍然可以在應用程式的第一個版本中使用“update”,但上述方法意味著您有機會盡早測試您的初始變更日誌。

此時,值得回顧一下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。要升級生產資料庫,您需要為您的領域模型更改建立變更日誌。您可以手動完成此操作,但透過使用資料庫遷移外掛的“diff”命令,您可以節省大量精力。

要執行差異,您需要一個數據庫,外掛可以將其與當前領域模型進行比較。同樣,最好使用與生產環境中使用的相同型別的資料庫。此外,資料庫必須處於其原始狀態,即在當前領域模型更改之前。換句話說,不要在啟用了dbCreate = "update"的情況下執行您的應用程式!事實上,為應用程式的每個版本建立一個數據庫轉儲是值得的,這樣您就可以在不小心以這種方式更新資料庫時進行回滾。

好了,警告就到此為止。讓我們建立下一個變更日誌

grails prod dbm-gorm-diff --add changelog-1.0.1.groovy

和以前一樣,這將在遷移目錄中建立一個changelog-1.0.1.groovy檔案,並將其包含在父變更日誌中。您還需要檢查生成的變更日誌並可能進行調整,但編輯現有檔案比從頭開始建立新檔案要容易得多。就是這樣!現在您可以將此變更日誌與相應的領域類更改一起提交到版本控制。事實上,我建議您始終在同一個提交中包含領域類更改和相應的遷移指令碼修改。

變更日誌檔案本身如下所示

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
            }
        }
    }
}

如您所見,它們只是變更集的集合,每個變更集都包含一些資料庫重構。每個變更集都需要每個作者在每個變更日誌中具有唯一的 ID,以便 Liquibase 可以跟蹤它是否已應用(Liquibase 跟蹤變更集,而不是變更日誌!)。上面的示例演示瞭如何更新現有資料(id “UpdateDescriminatorForPluginTabs”)以及新增新列。其他支援的重構包括

  • 新增/重新命名/修改列(schema 更改)
  • 新增索引
  • 新增/重新命名/修改表
  • 新增/刪除唯一約束

完整的重構範圍在Liquibase 手冊中有所描述,儘管所有示例都是 XML 格式。幸運的是,XML -> Groovy 的對映非常簡單

  1. XML 元素 -> Groovy 方法
  2. 屬性 -> 命名引數
  3. 巢狀元素 -> 閉包內的巢狀方法

最後,您可能想知道如何組織變更日誌。您應該為每個重構設定一個變更集嗎?還是為每個變更日誌設定一個變更集?或者介於兩者之間?這取決於您,但每個原始碼控制提交一個變更集可能效果很好。換句話說,您為每個包含領域模型更改的提交建立一個變更集。或者,您可能希望每個資料庫表一個變更集。做任何適合您的。

本文到此結束。正如您所看到的,適當的資料庫遷移支援是任何生產資料庫支援的 Web 應用程式的重要組成部分,因此我們現在擁有一個官方支援且功能強大的外掛來滿足這一需求,這是一個好訊息。它只適用於關係型資料庫(所以恐怕沒有 Redis、MongoDB 等的遷移),但它仍然應該滿足絕大多數 Grails 使用者的需求。快去試試吧!

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有