首先你須要明白Evolution的做用是什麼?它可讓你經過幾個腳本文件,輕鬆完成數據庫的管理工做。你只負責編寫腳本,腳本和數據庫之間的同步工做,Evolution幫你搞定。 java
1、如何開啓Evolution插件? sql
play默認是啓用Evolution插件的,若是想禁用Evolution插件,在conf/application.conf中添加配置項evolutionplugin=disabled,或者設置經過設置系統屬性的方式-Devolutionplugin=disabled。禁用Evolution插件至關於切斷了play與數據庫間的同步手段,實體類的任意變更都不會影響到數據庫的表結構,這在項目發佈時很是有用。 數據庫
2、Evolution腳本存放位置 瀏覽器
Evolution腳本在項目中的路徑爲conf/evolutions/{database name},例如對於默認的default數據庫,路徑爲conf/evolutions/default。Evolution腳本能夠有不少個,腳本名爲連續的數字,從1開始。例如,第1個腳本1.sql,第2個腳本2.sql,如此類推...。 服務器
3、Evolution腳本格式 app
Evolution腳本包含三個部分:註釋,up腳本和down腳本。 測試
1. 註釋 ui
在標記# --- !Ups以上的都是註釋部分,標記中的---不是必須的,只要包含#!Ups就能夠了,即標記模式要知足^#.*!Ups.*$,一樣地,標記# --- !Downs也是相似的。註釋部分沒有格式限制,能夠隨意書寫。 編碼
2. up腳本 spa
在標記# --- !Ups和# --- !Downs之間的部分是up腳本,up腳本是一段用來初始化或更新數據庫的sql腳本,每一條sql語句必須以分號;結尾,若是sql語句中含有分號,須要使用;;進行轉義。註釋方法遵循標準sql,單行註釋使用--,多行註釋使用/* ... */。
3. down腳本
標記# --- !Downs以後的部分是down腳本,down腳本是一段撤銷腳本,相似於數據庫中的事務回滾,將數據庫恢復到up腳本執行以前的狀態。書寫規則同up腳本。
4、Evolution配置表PLAY_EVOLUTIONS
Evolution插件使用表PLAY_EVOLUTIONS管理同步腳本。在項目第一次啓動時,Evolution插件會在數據庫中建立PLAY_EVOLUTIONS表,比較惋惜的是,Evolution插件並無根據不一樣的數據庫類型生成不一樣的建表語句,而是硬編碼了下面的建表語句:
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 )
PLAY_EVOLUTIONS表包含7個字段,解釋以下:
- id: 惟一對應一個腳本文件名,也成爲revision,值從1開始
- hash:apply_script和revert_script兩個字段內容拼接後的sha1哈希值,用來檢測腳本內容是否發生變化
- applied_at:記錄up或down腳本執行時間
- apply_script:存放腳本文件中的up腳本
- revert_script:存放腳本文件中的up腳本
- state:保存當前的執行狀態,值能夠爲:applied/applying_up/applying_down
- last_problem: 存放腳本執行時錯誤信息
每一個數據庫的Evolution腳本文件數和相應PLAY_EVOLUTIONS表中記錄條數相同,而且是一一對應關係,對應關係爲文件名和id相同。
5、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。
6、常見問題解決方法
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文件的頭兩行註釋:
7、不一樣運行模式下的差別
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}=true和applyDownEvolutions.{database name}=true,則自動執行本次Evolution操做;
3)若是本次Evolution操做涉及down腳本,而且沒有同時配置applyEvolutions.{database name}=true和applyDownEvolutions.{database name}=true兩個參數,則直接拋出InvalidDatabaseRevision異常。
8、Evolution with Oracle
在play第一次鏈接數據庫時,Evolution插件會嘗試建立PLAY_EVOLUTIONS表,上文曾提到過,Evolution插件以硬編碼形式提供的建表語句沒法在Oracle中執行,緣由是Oracle中沒有text類型,因此在將play的數據源切換至Oracle時,咱們須要手工在Oracle上建立PLAY_EVOLUTIONS表,建表語句以下:
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註解生成另一個名稱:
@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); }
9、小結
Evolution插件整體仍是使人比較滿意的,遺憾的是在鏈接Oracle數據源時須要手工干預。但願在之後版本中,Evolution插件可以自動判斷數據庫類型,儘可能減小人爲的手工干預。
10、參考
- http://www.playframework.com/documentation/2.1.1/Evolutions