play framwork Database 'default' needs evolution!

首先你需要明白Evolution的作用是什麼?它可以讓你通過幾個腳本文件,輕鬆完成數據庫的管理工作。你只負責編寫腳本,腳本和數據庫之間的同步工作,Evolution幫你搞定。

一、如何開啓Evolution插件?

    play默認是啓用Evolution插件的,如果想禁用Evolution插件,在conf/application.conf中添加配置項evolutionplugin=disabled,或者設置通過設置系統屬性的方式-Devolutionplugin=disabled禁用Evolution插件相當於切斷了play與數據庫間的同步手段,實體類的任意變動都不會影響到數據庫的表結構,這在項目發佈時非常有用。

二、Evolution腳本存放位置

    Evolution腳本在項目中的路徑爲conf/evolutions/{database name},例如對於默認的default數據庫,路徑爲conf/evolutions/default。Evolution腳本可以有很多個,腳本名爲連續的數字,從1開始。例如,第1個腳本1.sql,第2個腳本2.sql,如此類推...。

三、Evolution腳本格式

    Evolution腳本包含三個部分:註釋,up腳本和down腳本。

1. 註釋

在標記# --- !Ups以上的都是註釋部分,標記中的---不是必須的,只要包含#!Ups就可以了,即標記模式要滿足^#.*!Ups.*$,同樣地,標記# --- !Downs也是類似的。註釋部分沒有格式限制,可以隨意書寫。

2. up腳本

在標記# --- !Ups# --- !Downs之間的部分是up腳本,up腳本是一段用來初始化或更新數據庫的sql腳本,每一條sql語句必須以分號;結尾,如果sql語句中含有分號,需要使用;;進行轉義。註釋方法遵循標準sql,單行註釋使用--,多行註釋使用/* ... */。

3. down腳本

標記# --- !Downs之後的部分是down腳本,down腳本是一段撤銷腳本,類似於數據庫中的事務回滾,將數據庫恢復到up腳本執行之前的狀態。書寫規則同up腳本。

四、Evolution配置表PLAY_EVOLUTIONS

    Evolution插件使用PLAY_EVOLUTIONS管理同步腳本。在項目第一次啓動時,Evolution插件會在數據庫中創建PLAY_EVOLUTIONS表,比較可惜的是,Evolution插件並沒有根據不同的數據庫類型生成不同的建表語句,而是硬編碼了下面的建表語句:

?
1
2
3
4
5
6
7
8
9
create table play_evolutions (
    id int not null primary key,
    hash varchar(255) not null,
    applied_at timestamp not null,
    apply_script text,
    revert_script text,
    state varchar(255),
    last_problem text
)


    如果連接的是Oracle數據庫,這條語句執行會報錯,因爲Oracle不認識text類型。下文會講到如何針對Oracle手工修改建表語句。


PLAY_EVOLUTIONS表包含7個字段,解釋如下:

    -    id: 唯一對應一個腳本文件名,也成爲revision,值從1開始

    -    hash:apply_scriptrevert_script兩個字段內容拼接後的sha1哈希值,用來檢測腳本內容是否發生變化

    -    applied_at:記錄up或down腳本執行時間

    -    apply_script:存放腳本文件中的up腳本

    -    revert_script:存放腳本文件中的up腳本

    -    state:保存當前的執行狀態,值可以爲:applied/applying_up/applying_down

    -    last_problem: 存放腳本執行時錯誤信息

每個數據庫的Evolution腳本文件數和相應PLAY_EVOLUTIONS表中記錄條數相同,並且是一一對應關係,對應關係爲文件名和id相同。

五、Evolution插件執行過程分析

針對conf/application.conf配置的每個數據源依次執行:

1. 在conf/evolutions/{database name}目錄下,依次尋找1.sql,2.sql,...,只至發現某個文件不存在爲止,例如目錄下有:0.sql,1.sql,2.sql,4.sql,則最終只會找到1.sql, 2.sql兩個文件,最後按文件名降序排列得到一個列表;

2. 查詢PLAY_EVOLUTIONS中所有記錄,按id降序排列得到一個列表;

3. 比較前兩步得到的兩個列表:

    1)如果有腳本文件在數據庫中不存在,則向PLAY_EVOLUTIONS插入一條記錄,並執行該腳本文件的up腳本;

    2)如果PLAY_EVOLUTIONS表中有記錄,但是該腳本文件卻不存在,則執行該條記錄的down腳本,並且刪除該條記錄

    3)如果腳本文件存在,並且PLAY_EVOLUTIONS表中也有相應記錄,則比較腳本文件的sha1(up腳本+down腳本)與表中記錄的hash值是否相等,如果相等,則不做任何處理;如果不等,則先執行表中記錄的down腳本,刪除該條記錄,重新插入一條與腳本文件對應的新記錄,執行up腳本。考慮到一個應用可能在多臺服務器上同時部署,在執行up/down腳本時,會先將表中相應記錄的state改爲applying_up/applying_down狀態,如果執行出錯,則更新last_problem字段,存入錯誤描述,狀態保持不變,如果執行成功,則將狀態更新成applied。

六、常見問題解決方法    

1. 瀏覽器老是提示"Database xxx needs evolution!", 則在conf/application.conf中添加配置applyEvolutions.{database name}=true,即可解決。

2. up/down腳本執行出錯後,啓動項目瀏覽器總是提示"Database xxx is in inconsistent state!", 如果有腳本執行失敗,則Evolution插件不會再嘗試執行出錯的腳本,而是直接在瀏覽器中報錯,此時的解決辦法是手工在數據庫中執行出錯腳本,然後再單擊頁面上的"Mark it resolved"按鈕。

3. Ebean每次都會重新生成1.sql文件,如何手工修改1.sql,而不是用Ebean的自動生成腳本?

    刪除1.sql文件的頭兩行註釋:

   

七、不同運行模式下的差異

1. 測試模式下(Mode.Test),無視配置參數,任意的Evolution操作都會被直接執行。

2. 開發模式下(Mode.Dev),如果配置了applyEvolutions.{database name}=true,則自動執行本次Evolution操作,否則會在瀏覽器中提示"Database xxx needs evolution!"

3. 產品模式下(Mode.Prod)情況比較複雜,根據配置參數分三種情況:

    1)如果本次Evolution操作不涉及down腳本,並且配置了applyEvolutions.{database name}=true,則自動執行本次Evolution操作;

    2)如果本次Evolution操作涉及down腳本,並且配置了applyEvolutions.{database name}=trueapplyDownEvolutions.{database name}=true自動執行本次Evolution操作;

    3)如果本次Evolution操作涉及down腳本,並且沒有同時配置applyEvolutions.{database name}=trueapplyDownEvolutions.{database name}=true兩個參數,則直接拋出InvalidDatabaseRevision異常。

八、Evolution with Oracle

    在play第一次連接數據庫時,Evolution插件會嘗試創建PLAY_EVOLUTIONS表,上文曾提到過,Evolution插件以硬編碼形式提供的建表語句無法在Oracle中執行,原因是Oracle中沒有text類型,所以在將play的數據源切換至Oracle時,我們需要手工在Oracle上創建PLAY_EVOLUTIONS表,建表語句如下:

?
1
2
3
4
5
6
7
8
9
create table play_evolutions (
    id number not null primary key,
    hash varchar2(255) not null,
    applied_at timestamp not null,
    apply_script clob,
    revert_script clob,
    state varchar2(255),
    last_problem clob
)

    這裏會有個問題,apply_script和revert_script存放的是up和down腳本,有時腳本會很大,而很多數據庫都會限制text類型必須小於64kb,就算選擇Oracle的clob類型也必須小於4000kb,較通用的解決辦法是將大的腳本文件分成幾個較小的腳本文件。

    另外需要注意的是,Oracle中字段名不能超過30個字符,不要使用實體映射的默認表名,例如User/Role,最好使用@Table註解生成另外一個名稱:

?
1
2
3
4
5
6
7
8
9
10
@Entity
@Table(name="r_user")
public class User extends Model {
    @Id
    public Long id;
     
    @Constraints.Required
    public String name;
    public static Finder<Long,User> find = new Finder<Long,User>(Long.class, User.class);
}


九、小結

    Evolution插件總體還是令人比較滿意的,遺憾的是在連接Oracle數據源時需要手工干預。希望在以後版本中,Evolution插件能夠自動判斷數據庫類型,儘量減少人爲的手工干預。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章