文件

§管理資料庫演化

當您使用關聯式資料庫時,您需要一種方法來追蹤和整理您的資料庫結構演化。通常有幾種情況需要您使用更精密的技術來追蹤資料庫結構變更

§啟用演化

evolutionsjdbc 加入您的相依性清單中。例如,在 build.sbt

libraryDependencies ++= Seq(evolutions, jdbc)

§使用編譯時 DI 執行演化

如果您使用 編譯時相依性注入,您需要將 EvolutionsComponents 特質混入您的 cake 中,以存取 ApplicationEvolutions,它會在實例化時執行演化。EvolutionsComponents 需要定義 dbApi,您可以透過混入 DBComponentsHikariCPComponents 來取得。由於 applicationEvolutions 是由 EvolutionsComponents 提供的 lazy val,您需要存取該 val 以確保執行演化。例如,您可以在 ApplicationLoader 中明確存取它,或從其他元件建立明確的相依性。

您的模型需要 Database 的實例才能連線到您的資料庫,這可以從 dbApi.database 取得。

import play.api.db.evolutions.EvolutionsComponents
import play.api.db.DBComponents
import play.api.db.Database
import play.api.db.HikariCPComponents
import play.api.routing.Router
import play.api.ApplicationLoader.Context
import play.api.BuiltInComponentsFromContext
import play.filters.HttpFiltersComponents

class AppComponents(cntx: Context)
    extends BuiltInComponentsFromContext(cntx)
    with DBComponents
    with EvolutionsComponents
    with HikariCPComponents
    with HttpFiltersComponents {
  // this will actually run the database migrations on startup
  applicationEvolutions

}

§演化指令碼

Play 使用多個演化指令碼追蹤您的資料庫演化。這些指令碼以純粹的 SQL 編寫,而且預設應位於您應用程式的 conf/evolutions/{資料庫名稱} 目錄中。如果演化套用於您的預設資料庫,這個路徑為 conf/evolutions/default

第一個指令碼命名為 1.sql,第二個指令碼命名為 2.sql,以此類推…

每個指令碼包含兩個部分

例如,看看這個啟動基本應用程式的第一個演化指令碼

-- Users schema

-- !Ups

CREATE TABLE User (
    id bigint(20) NOT NULL AUTO_INCREMENT,
    email varchar(255) NOT NULL,
    password varchar(255) NOT NULL,
    fullname varchar(255) NOT NULL,
    isAdmin boolean NOT NULL,
    PRIMARY KEY (id)
);

-- !Downs

DROP TABLE User;

UpsDowns 部分使用標準的單行 SQL 註解區分,分別包含 !Ups!Downs。SQL92 (--) 和 MySQL (#) 註解樣式都受支援,但我們建議使用 SQL92 語法,因為它受更多資料庫支援。

Play 會將你的 .sql 檔案分割成一系列以分號分隔的陳述式,然後逐一對資料庫執行。因此,如果你需要在陳述式使用分號,請輸入 ;; 取代 ; 來跳脫。例如,INSERT INTO punctuation(name, character) VALUES ('semicolon', ';;');

如果在 application.conf 中設定了資料庫且存在演化腳本,則會自動啟用演化。你可以透過設定 play.evolutions.enabled=false 來停用它們。例如,當測試設定自己的資料庫時,你可以為測試環境停用演化。

當演化啟用時,Play 會在開發模式中的每次要求之前,或在生產模式中啟動應用程式之前,檢查你的資料庫架構狀態。在開發模式中,如果你的資料庫架構不是最新版本,錯誤頁面會建議你透過執行適當的 SQL 腳本來同步你的資料庫架構。

如果你同意 SQL 腳本,你可以按一下「套用演化」按鈕直接套用它。

§演化設定

演化可以在全域和每個資料來源中設定。對於全域設定,金鑰應以 play.evolutions 為前綴。對於每個資料來源設定,金鑰應以 play.evolutions.db.<datasourcename> 為前綴,例如 play.evolutions.db.default。支援下列設定選項

例如,若要為所有演化啟用 autoApply,您可以在 application.conf 或系統屬性中設定 play.evolutions.autoApply=true。若要停用名為 default 的資料來源的自動提交,請設定 play.evolutions.db.default.autocommit=false

§演化腳本的位置

如前所述,預設情況下,進化腳本位於應用程式的 conf/evolutions/{資料庫名稱} 目錄中。但是,您可以變更資料夾的名稱:例如,如果您想將預設資料庫的腳本儲存在 conf/db_migration/default 中,您可以透過設定 path 設定檔來執行此操作

# For the default database
play.evolutions.db.default.path = "db_migration"

# Or, if you want to set the location for all databases
play.evolutions.db.path = "db_migration"

Play 永遠可以載入這些腳本,因為 conf 資料夾的內容在開發模式和 生產模式 中都位於類別路徑中。

您也可以將進化腳本儲存在專案資料夾外部,並使用絕對路徑或相對路徑來參照它們

# Absolute path:
play.evolutions.db.default.path = "/opt/db_migration"
# Relative path (as seen from your project's root folder)
play.evolutions.db.default.path = "../db_migration"

但是,請注意,當使用此類設定檔時,如果您決定組合一個,進化腳本將不會包含在生產套件中,因此您必須在生產中管理進化腳本。

最後,您可以將進化腳本儲存在專案中,但位於 conf 資料夾外部

play.evolutions.db.default.path = "./db_migration"

在這種情況下,腳本將不會位於類別路徑中。在開發過程中,這並不重要,因為在類別路徑中尋找進化腳本之前,Play 會在檔案系統中尋找它們(這就是上述絕對路徑和相對路徑方法可行的原因)。
但是,不將進化資料夾放在 conf 目錄中,因此也不在類別路徑中,表示它不會打包用於生產。因此,為了確保資料夾已打包並在生產中可用,您必須在 build.sbt 中設定它

Universal / mappings ++= (baseDirectory.value / "db_migration" ** "*").get.map {
  (f: File) => f -> f.relativeTo(baseDirectory.value).get.toString
}

§變數替換

您可以在進化腳本中定義將以其替換項取代的佔位符,這些替換項定義在 application.conf

play.evolutions.db.default.substitutions.mappings = {
  table = "users"
  name = "John"
}

像這樣的進化腳本

INSERT INTO $evolutions{{{table}}}(username) VALUES ('$evolutions{{{name}}}');

現在將變成

INSERT INTO users(username) VALUES ('John');

在套用進化時。

進化元資料表將包含原始 SQL 腳本, 會替換佔位符。

變數替換區分大小寫,因此 $evolutions{{{NAME}}}$evolutions{{{name}}} 相同。

您也可以變更佔位符語法的字首和字尾

# Change syntax to @{...}
play.evolutions.db.default.substitutions.prefix = "@{"
play.evolutions.db.default.substitutions.suffix = "}"

evolution 模組也支援跳脫,適用於不應替換變數的情況。此跳脫機制預設為啟用。若要停用,您需要設定

play.evolutions.db.default.substitutions.escapeEnabled = false

如果啟用,語法 !$evolutions{{{...}}} 可用於跳脫變數替換。例如

INSERT INTO notes(comment) VALUES ('!$evolutions{{{comment}}}');

不會以其替換項取代,而是會變成

INSERT INTO notes(comment) VALUES ('$evolutions{{{comment}}}');

在最後的 sql 中。

此跳脫機制將套用至所有 !$evolutions{{{...}}} 佔位符,無論是否在 substitutions.mappings 設定檔中定義變數的對應。

§同步並行變更

現在讓我們假設有兩位開發人員處理這個專案。Jamie 將處理需要新增資料庫表格的功能。因此 Jamie 將建立下列 2.sql evolution 腳本

-- Add Post

-- !Ups
CREATE TABLE Post (
    id bigint(20) NOT NULL AUTO_INCREMENT,
    title varchar(255) NOT NULL,
    content text NOT NULL,
    postedAt date NOT NULL,
    author_id bigint(20) NOT NULL,
    FOREIGN KEY (author_id) REFERENCES User(id),
    PRIMARY KEY (id)
);

-- !Downs
DROP TABLE Post;

Play 會將此 evolution 腳本套用至 Jamie 的資料庫。

另一方面,Robin 將處理需要變更 User 表格的功能。因此 Robin 也會建立下列 2.sql evolution 腳本

-- Update User

-- !Ups
ALTER TABLE User ADD age INT;

-- !Downs
ALTER TABLE User DROP age;

Robin 完成功能並提交(假設使用 Git)。現在 Jamie 必須在繼續之前合併 Robin 的工作,因此 Jamie 執行 git pull,而合併發生衝突,如下所示

Auto-merging db/evolutions/2.sql
CONFLICT (add/add): Merge conflict in db/evolutions/2.sql
Automatic merge failed; fix conflicts and then commit the result.

每位開發人員都建立一個 2.sql evolution 腳本。因此 Jamie 需要合併此檔案的內容

<<<<<<< HEAD
-- Add Post

-- !Ups
CREATE TABLE Post (
    id bigint(20) NOT NULL AUTO_INCREMENT,
    title varchar(255) NOT NULL,
    content text NOT NULL,
    postedAt date NOT NULL,
    author_id bigint(20) NOT NULL,
    FOREIGN KEY (author_id) REFERENCES User(id),
    PRIMARY KEY (id)
);

-- !Downs
DROP TABLE Post;
=======
-- Update User

-- !Ups
ALTER TABLE User ADD age INT;

-- !Downs
ALTER TABLE User DROP age;
>>>>>>> devB

合併非常容易

-- Add Post and update User

-- !Ups
ALTER TABLE User ADD age INT;

CREATE TABLE Post (
    id bigint(20) NOT NULL AUTO_INCREMENT,
    title varchar(255) NOT NULL,
    content text NOT NULL,
    postedAt date NOT NULL,
    author_id bigint(20) NOT NULL,
    FOREIGN KEY (author_id) REFERENCES User(id),
    PRIMARY KEY (id)
);

-- !Downs
ALTER TABLE User DROP age;

DROP TABLE Post;

此 evolution 腳本代表資料庫的新版次 2,與 Jamie 已套用的前一版次 2 不同。

因此 Play 會偵測它並要求 Jamie 同步資料庫,方法是先還原已套用的舊版次 2,再套用新版次 2 腳本

§不一致狀態

有時您會在演化腳本中犯錯,而它們會失敗。在這種情況下,Play 會將您的資料庫架構標記為不一致狀態,並要求您在繼續之前手動解決問題。

例如,此演化的 Ups 腳本有錯誤

-- Add another column to User

-- !Ups
ALTER TABLE Userxxx ADD company varchar(255);

-- !Downs
ALTER TABLE User DROP company;

因此嘗試套用此演化會失敗,而 Play 會將您的資料庫架構標記為不一致

現在在繼續之前,您必須修正此不一致。因此,您執行已修正的 SQL 指令

ALTER TABLE User ADD company varchar(255);

… 然後按一下按鈕將此問題標記為手動解決。

但是由於您的演化腳本有錯誤,您可能想要修正它。因此,您修改 3.sql 腳本

-- Add another column to User

-- !Ups
ALTER TABLE User ADD company varchar(255);

-- !Downs
ALTER TABLE User DROP company;

Play 會偵測到這個取代先前 3 個的全新演化,並執行適當的腳本。現在一切都已修正,您可以繼續工作。

不過,在開發模式中,通常更簡單的方法是直接刪除您的開發資料庫,並從頭重新套用所有演化。

§交易性 DDL

預設情況下,每個演化腳本的每個陳述式都會立即執行。如果您的資料庫支援 交易性 DDL,您可以在 application.conf 中設定 evolutions.autocommit=false 以變更此行為,導致 **所有** 陳述式僅在 **一個交易** 中執行。現在,當演化腳本無法在停用自動提交的情況下套用時,整個交易會回滾,且不會套用任何變更。因此,您的資料庫會保持「乾淨」,且不會變得不一致。這讓您能輕鬆修正演化腳本中的任何 DDL 問題,而無需像上述那樣手動修改資料庫。

§演化儲存和限制

演進會儲存在資料庫中名為 play_evolutions 的表格中。文字欄位會儲存實際的演進腳本。您的資料庫在文字欄位上可能有限制 64kb 的大小。若要解決 64kb 的限制,您可以:手動變更 play_evolutions 表格結構,變更欄位類型,或(建議)建立多個小於 64kb 的演進腳本。

下一步:伺服器後端


在此文件檔中發現錯誤?此頁面的原始程式碼可以在 這裡 找到。在閱讀完 文件檔指南 後,請隨時提交拉取請求。有任何問題或建議想分享?請前往 我們的社群論壇,與社群展開討論。