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插件能够自动判断数据库类型,尽量减少人为的手工干预。



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