Play 2.1 - 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插件并没有根据不同的数据库类型生成不同的建表语句,而是硬编码了下面的建表语句:

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_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相同。

五、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}=true和applyDownEvolutions.{database name}=true,则自动执行本次Evolution操作;

    3)如果本次Evolution操作涉及down脚本,并且没有同时配置applyEvolutions.{database name}=true和applyDownEvolutions.{database name}=true两个参数,则直接抛出InvalidDatabaseRevision异常。

八、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); 
}

九、小结

    Evolution插件总体还是令人比较满意的,遗憾的是在连接Oracle数据源时需要手工干预。希望在以后版本中,Evolution插件能够自动判断数据库类型,尽量减少人为的手工干预。

十、参考

    - http://www.playframework.com/documentation/2.1.1/Evolutions

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏web前端

HTTP协议理解

HTTP(Hyper Text Transfer Protocol)超文本传输协议,是一种请求响应式协议,类似两国会晤中需要遵守的规则。那么,其中有什么内容特点...

2178
来自专栏L宝宝聊IT

mysql主从复制与读写分离示例

2224
来自专栏你不就像风一样

使用hibernate造成的MySql 8小时问题解决方案

mysql5之前的版本,可以在jdbc连接的url中加入:autoReconnect = true

801
来自专栏云计算教程系列

如何保护PostgreSQL免受攻击

服务器刚搭建,流量少,没有任何对黑客有价值的东西,你可能就会忽视相关的安全问题。但是,许多漏洞攻击都是自动化的,专门用于查找你服务中的BUG。这些服务器主要目的...

1303
来自专栏决胜机器学习

《高性能MySQL》读书笔记(一) ——MySQL架构及重要属性概述

《高性能MySQL》读书笔记(一)——MySQL架构及重要属性概述 (原创内容,转载请注明来源,谢谢) 一、MySQL逻辑架构 1、三层架构 ...

4519
来自专栏五毛程序员

wamp+thinkphp环境配置

3655
来自专栏Java技术交流群809340374

分布式锁的3种实现(数据库、缓存、Zookeeper)

目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。

1610
来自专栏程序猿DD

12 条用于 Linux 的 MySQL/MariaDB 安全最佳实践

MySQL 是世界上最流行的开源数据库系统,MariaDB(一个 MySQL 分支)是世界上增长最快的开源数据库系统。在安装 MySQL 服务器之后,在默认配置...

37610
来自专栏文渊之博

xtrabackup部署以及使用

  备份mysql数据库一直是一个比较恶心的工作,主要就是备份的数据库比较大实在是慢。最近开始使用xtrabackup来备份数据库,速度上快了很多,尤其还原速度...

1353
来自专栏铭毅天下

探究 | Elasticsearch如何物理删除给定期限的历史数据?

想到删除,基础认知是delete,细分为删除文档(document)和删除索引;要删除历史数据,基础认知是:删除了给定条件的数据,用delete_by_quer...

4421

扫码关注云+社区

领取腾讯云代金券