Play Framework的数据库迁移方案——Evolutions

只要有个数据库,都需要迁移(Migration)方案,除非一版定稿、再不修改。

Play Framework在1.x时代,使用的是Module migrate; 而在2.x时代,使用的是Evolutions。 本文介绍Evolutions的原理与配置。

Evolutions简介

EvolutionsPlay自带的一个组件,负责实现数据库Schema的变更管理。 在使用Slick的情况下,Play还需要一个额外的[play-slick-evolutions]来对接Evolutions。 [play-slick-evolutions]是play-slick的一个附带组件。

它和Module migrate比较类似,通过维护一系列的1.sql2.sql等,来保留数据库Schema演进的状态。 这些SQL需要手写,并且人工确保和ORM代码保持一致。 Evolutions会根据这些SQL,提供半自动或全自动的数据库维护操作。 在数据库中,会额外多一个play_evolutions表,记录升降级状态。

半自动操作大概是这样的:

needs evolution

根据提示,在开发时手动点击【Apply this script now!】,就可以执行SQL、升级数据库。 也会出现其它类型的提示,都可以根据提示来操作。

全自动操作只需要增加一行配置,见下一节。 原理大概是通过Evolutions自带的依赖注入,利用Slick完成数据库操作。

Evolutions配置

官网提供的配置,主要是针对内存数据库。 对Slick场景的Evolutions配置,语焉不详。

实际上,在build.sbt中主要就是加两行:

libraryDependencies ++= Seq(
    "com.typesafe.play" %% "play-slick" % "4.0.0",
    "com.typesafe.play" %% "play-slick-evolutions" % "4.0.0",
)

这里要注意,必须把jdbcevolutions删除。 前者有依赖注入的冲突,后者完全可以去掉。

虽然Play官网的2.7文档写着,上面两个依赖应该使用3.0.0版本。 但是根据play-slick源码的README,在Slick3.3.1的情况下,应该使用4.0.0

Plugin version Play version Slick version Scala version
5.0.x 2.8.x 3.3.2+ 2.12.x/2.13.x
4.0.2+ 2.7.x 3.3.2+ 2.11.x/2.12.x/2.13.x
4.0.x 2.7.x 3.3.x 2.11.x/2.12.x
3.0.x 2.6.x 3.2.x 2.11.x/2.12.x
2.1.x 2.5.x 3.2.0 2.11.x
2.0.x 2.5.x 3.1.0 2.11.x
1.1.x 2.4.x 3.1.0 2.10.x/2.11.x
1.0.1 2.4.x 3.0.1 2.10.x/2.11.x
1.0.0 2.4.x 3.0.0 2.10.x/2.11.x
0.8.x 2.3.x 2.1.0 2.10.x/2.11.x
0.7.0 2.3.x 2.0.2 2.10.x
0.6.1 2.2.x 2.0.x 2.10.x
0.5.1 2.2.x 1.0.x 2.10.x

build.sbt配置完毕后,conf/application.conf中只需要添加两行即可:

play.evolutions.enabled=true
play.evolutions.autoApply=true

第一句其实默认就是true,但求心安。 第二句是自动升级,避免手动点击调试页面的尴尬。 如果可以,第二句最好只在生产环境的某一个服务配置,避免服务更新与动态扩容时对数据库的冲击。

SQL

Evolutions的SQL,需要放置在conf/evolutions/default/下,以1.sql2.sql方式命名。 default是数据库的默认配置名。 如果Slick配置中支持了多数据库,这里也支持按数据库名称配置多个目录的升降级SQL。

conf/evolutions/
└── default
    ├── 1.sql
    └── 2.sql

PostgreSQL的示例如下。 1.sql内容:

-- Users schema
-- !Ups

CREATE TABLE IF NOT EXISTS "USER" (
    "id" bigserial NOT NULL PRIMARY KEY,
    "email" varchar(255) NOT NULL,
    "password" varchar(255) NOT NULL,
    "fullname" varchar(255) NOT NULL,
    "isAdmin" boolean NOT NULL
);

-- !Downs
DROP TABLE IF EXISTS "USER";

2.sql内容:

-- Add Post and update User
-- !Ups

ALTER TABLE IF EXISTS "USER"
    ADD COLUMN IF NOT EXISTS "age" int;

CREATE TABLE IF NOT EXISTS "Post" (
    "id" bigserial NOT NULL PRIMARY KEY,
    "title" varchar(255) NOT NULL,
    "content" text NOT NULL,
    "postedAt" date NOT NULL,
    "author_id" bigint NOT NULL,
    FOREIGN KEY ("author_id") REFERENCES "USER" ("id")
);

-- !Downs
ALTER TABLE IF EXISTS "USER"
    DROP COLUMN IF EXISTS "age";

DROP TABLE IF EXISTS "Post";

其中,第一行注释是写给人看的描述,执行时无实质用途。 -- !Ups后面是升级时执行的内容,而-- !Downs是降级时执行的内容。 和Play 1.x时代的Module migrate相比,这种去掉create.sql、单文件完成一次升降级的设计,更加简洁。

开发时,确保每次提交数据库Schema变更操作时,都包含对应的x.sql文件, x按既有次数递增。 这样,每一个数据库状态,就都被记录下来,技术上可以做到从任意版本到另一版本的升降级。

注意:这个文件中不要乱加注释,否则会影响Evolutions的解析,出现额外错误。

推荐使用pgformatter格式化。

查询升级情况

通过以下select语句,可以在数据库中查询到Evolutions的运行结果。

# select id, hash, applied_at, state, last_problem from play_evolutions;
 id |                   hash                   |       applied_at        |  state  | last_problem
----+------------------------------------------+-------------------------+---------+--------------
  1 | 128a219f18c09bdee2246e68dbbd74e177d32deb | 2020-08-19 19:42:57.503 | applied |
  2 | 362b9bcdc8586247f2a74917e0f6d1d41f058358 | 2020-08-19 19:42:58.134 | applied |

参考

资料比较少,除了官网,基本上只有上GitHub去看README和源码了。


相关笔记