被关系数据库建表和升级折磨?因为你没用大道至简的Flyway

编程

关系数据库之殇

    你在使用关系数据库的过程中,是否曾经遇到以下情况,甚至因此一度想要放弃或已经放弃关系数据库?

场景一:开发环境,多人共用一套数据库

开发正调试着,忽然代码报错“XX字段不存在”:谁TMD又把表结构给改了…

场景二:开发环境,每个人各自搭建自己的数据库

开发完一个功能,提交代码、更新,重启准备调试下,代码报错“XX表不存在”

吼一嗓子:谁又改表结构了?什么?每个人都要把xxx.sql执行一遍?

...

新员工:我要搭一套开发数据库,到底应该执行哪些SQL脚本?

场景三:开发转测试

测试:你看这个功能是不是有个Bug?

开发1:哦,你要执行一下这个SQL脚本。

测试:嗯,现在没问题了,但是怎么保证这个脚本没有Bug,我能再重现、测试一遍吗?

开发:额~,你重新搭一遍数据库吧...

场景四:搭建一套演示环境

执行SQL脚本1、SQL脚本2、SQL脚本3…启动服务失败!

什么?这个脚本N是测试版本的,war包是已经上线的版本?

删库再来一遍...

场景五:放弃关系数据库的坑

受不了关系数据库了,我们切MongoDB吧…嗯,控制台果然清静了

...

几个版本后:

生产环境某些数据查不到了、还有类型不匹配,神马?

A字段改过名,B字段换了个类型?

    如果上面的问题,你一个都没遇到过,要么是你所做的项目太简单,要么你们的开发流程非常规范,或者变更控制得太好了,本文你大可跳过了。

    如果你正被上面的问题困扰,你可能会因此想要入另一个坑:NoSQL,那么恭喜你将遇到更多的坑,比如关联查询问题、数据版本问题...

注:这里并不否定NoSQL的价值,各种NoSQL是关系数据库的良好补充。

但是如果想将NoSQL做为关系数据库的替代,那么你将会陷入比关系数据库还多的线上问题之中。

    如果你看到这里,说明你不想逃避问题,那么让我们一起来认识这个关系数据库升级管理的利器——Flyway

 

Flyway原理介绍

Flyway是什么?一句话概括,Flyway就是一个数据库版本管理组件。它的原理非常简单:

  1. 项目启动时拉起Flyway,先检查数据库里面有没有Flyway元数据表,没有则创建;
  2. 检查Flyway元数据表中的记录,哪些脚本已经执行过,当前版本是什么;
  3. 查找代码中的(名称满足规则的)数据库升级脚本,找出版本号大于(Flyway元数据)当前版本的脚本,逐个执行并记录执行结果到Flyway元数据表。

    你没看错,以上三点就是Flyway最核心的功能,我深信熟练掌握我另一篇博客《TDD两小时实现自定义表达式模板解析器》同学不出一天,就能自己实现这三点功能,对非JVM开发者我推荐在理解以上思想的基本上自己开发一套。

    大道至简,最简单的设计往往是最有效的。通过以上功能,我们可以很容易做到:

  1. 代码与数据库建表&升级脚本放在一起同步管理,通过代码(SQL)就可以了解到表结构;
  2. 无须人工执行任何脚本,运行代码或服务即可完成(数据库表结构的)环境搭建;
  3. 从任一版本的环境(表结构),都可以通过运行指定(新)版本的代码或服务来自动升级到指定新版本;
  4. (配合内存数据库/Docker/清库脚本)数据库搭建&升级脚本很容易与代码一起反复测试。

总之,用上Flyway之后,关系数据库的”关系“,不再是限制你开发效率的瓶颈,反而成为开发&测试的必要约定,提升版本质量的重要保障。

快速上手

不管工具多强大,如何用起来,是我们首要关心的,让我们以各种Java项目环境,来看一下如何在代码中将Flyway用起来,再由各位自己去细品Flyway对关系数据库版本管理带来的巨大改变。

注:所有示例都基于Maven,用Gradle的自己翻译下依赖,二者都不用的嘛...先研究下POM的依赖关系,再自己去下载jar包吧

1. 原生Java项目(不用Spring、Spring-Boot)

pom.xml文件中增加flyway的依赖:

    <dependency>

<groupId>org.flywaydb</groupId>

<artifactId>flyway-core</artifactId>

<!--通常情况下推荐最新发布版,此处是从旧代码中复制-->

<version>3.2.1</version>

</dependency>

java代码拉起Flyway:

DataSource dataSource = ...

...

//在数据连接创建之后,其它代码运行之前,先调用Flyway升级

Flyway flyway = new Flyway();

flyway.setDataSource(dataSource);

flyway.migrate();

...

编写建表脚本和数据初始化脚本

src

|-main

|-java

|-resources

|-db

|-migration

|-V0.0.1__init-schema.sql

|-V0.0.2__init-data.sql

注:脚本中的内容,就是正常的建表脚本,或者对上一版本的表结构变更、数据升级。

2. Spring项目

pom.xml文件中增加flyway的依赖(与原生java项目一样):

    <dependency>

<groupId>org.flywaydb</groupId>

<artifactId>flyway-core</artifactId>

<!--通常情况下推荐最新发布版,此处是从旧代码中复制-->

<version>3.2.1</version>

</dependency>

Spring配置文件拉起Flyway:

<!-- 创建Flyway的bean,并调用其migrate方法 -->

<bean id="flyway" class="org.flywaydb.core.Flyway" init-method="migrate">

<!-- 脚本文件校验和验证默认开启,可以防止脚本被修改。请视情况关闭校验 -->

<property name="validateOnMigrate" value="false" />

<property name="dataSource" ref="dataSource" />

</bean>

编写建表脚本和数据初始化脚本(与原生java项目一致)

src

|-main

|-java

|-resources

|-db

|-migration

|-V0.0.1__init-schema.sql

|-V0.0.2__init-data.sql

注:脚本中的内容,就是正常的建表脚本,或者对上一版本的表结构变更、数据升级。

3. Spring-Boot项目

Flyway已经被Spring-Boot整合,成为Spring标准的数据库升级工具,在Spring-Boot中使用Flyway更简单,只需添加依赖、编写数据库脚本即可,省去了拉起这一步。

pom.xml添加依赖(Spring-Boot已经整合,无须版本号)

    <dependency>

<groupId>org.flywaydb</groupId>

<artifactId>flyway-core</artifactId>

</dependency>

如果你使用IDEA,其还为你提供了创建Flyway升级脚本的功能,直接以当时日期时间为你生成SQL升级脚本:

生成的脚本名称如下:

src

|-main

|-java

|-resources

|-db

|-migration

|-V20190315174656__init-schema.sql

|-V20190315201742__init-data.sql

|-V20191225205157__update-userid-to-bitint.sql

4. 升级脚本示例

为Flyway编写的SQL脚本并没有什么特殊的要求,与正常SQL并无二致,只不过每个脚本编写时考虑的永远是对前一个版本表结构的升级,这也是传统方式下严谨的升级脚本应该满足的要求。

建表脚本示例:

BEGIN;

-- 新数据库建表

CREATE SCHEMA IF NOT EXISTS staff;

CREATE TABLE IF NOT EXISTS staff.staffs

(

id BIGINT AUTO_INCREMENT NOT NULL,

staffId VARCHAR(10) NOT NULL,

createTime TIMESTAMP NOT NULL,

lastUpdateTime TIMESTAMP NOT NULL,

name VARCHAR(50) NOT NULL,

PRIMARY KEY (id)

);

CREATE SCHEMA IF NOT EXISTS duty;

CREATE TABLE IF NOT EXISTS duty.onDutyDef

(

id BIGINT AUTO_INCREMENT NOT NULL,

name VARCHAR(50) NOT NULL,

startTime TIME NOT NULL,

endTime TIME NOT NULL,

PRIMARY KEY (id)

);

CREATE TABLE IF NOT EXISTS duty.breakDef

(

dutyId BIGINT NOT NULL,

name VARCHAR(10) NOT NULL,

startTime TIME NOT NULL,

endTime TIME NOT NULL,

PRIMARY KEY (dutyId, name)

);

-- 插入默认配置数据

INSERT INTO duty.onDutyDef

(name, startTime, endTime)

VALUES ("普通班", "09:00:00", "18:30:00");

INSERT INTO duty.breakDef

(dutyId, name, startTime, endTime)

VALUES (1, "午餐", "12:30:00", "14:00:00"),

(1, "晚餐", "18:30:00", "19:30:00");

COMMIT;

升级脚本示例

BEGIN;

-- 已有表字段变更

ALTER TABLE duty.signrecords ADD COLUMN clientId VARCHAR(40);

ALTER TABLE staff.staffs ADD COLUMN supervisor VARCHAR(100);

ALTER TABLE staff.staffs ADD COLUMN password VARCHAR(64);

-- 数据升级

UPDATE staff.staffs SET supervisor="00001,00002";

UPDATE staff.staffs SET password="8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92";

-- 新增表

CREATE SCHEMA IF NOT EXISTS users;

CREATE TABLE IF NOT EXISTS users.userroles (

staffId VARCHAR(15) NOT NULL,

rolename VARCHAR(15) NOT NULL,

PRIMARY KEY (staffId, rolename)

);

-- 新增表补充默认数据

INSERT INTO users.userroles(staffId,rolename)

VALUES

("00001","hr"),

("00001","supervisor"),

("00002","hr"),

("00002","supervisor"),

("00003","supervisor");

COMMIT;

推荐的搭档

根据这么多年我的使用经验,有下面这些用法,可以最大地发挥Flyway的作用

1. H2+Flyway

H2是一个纯Java实现的类似Derby的数据库,其最大的特点有三个:

  • 纯Java实现,只需在代码中引入一个jar文件,你就有了一个数据库;
  • 支持内存、文件、C/S三种模式;
  • 对SQL标准的兼容,以及对其它数据库的兼容模式。

以上三个特点,决定了它特别适合做持续集成,或者一键部署的项目:

  • 开发环境用内存模式,配合Flyway自动建表,与TDD一起带来极速的开发体验;
  • 测试环境用内存模式或文件模式,自动化测试一天重新运行个一百遍不是梦;
  • 生成环境用文件模式或C/S模式,(Web)服务器动态扩容也不是问题。

如果配合Spring-Boot的Profile机制,一套代码在开发环境、测试环境、生产环境完美无暇地切换:

#application.properties:

#主配置文件中配置好所有默认参数

spring.profiles.active=dev

spring.datasource.driver-class-name=org.h2.Driver

spring.datasource.initialize=false

flyway.baseline-version=0.0.0

flyway.baseline-on-migrate=true

flyway.validate-on-migrate=false

#application-dev.properties:

#开发环境使用内存模式,支持一键运行

spring.datasource.url=jdbc:h2:mem:kq

#application-test.properties:

#测试环境使用内存模式或文件模式,支持反复运行或数据持久化

spring.datasource.url=jdbc:h2:~/data/h2/kq

#保密要求高时,请使用JAVA虚拟机参数配置账号密码,如: -Dspring.datasource.username=test

spring.datasource.username=test

spring.datasource.password=123456

#application-prod.properties:

#生产环境建议所有数据库参数都使用JAVA虚拟机参数配置

spring.datasource.url=jdbc:h2:/var/lib/h2/kq

#尤其是账号密码,一定不要写死在配置文件中

#使用JAVA虚拟机参数配置账号密码,如: -Dspring.datasource.username=test

#开发环境运行项目:

mvn spring-boot:run

#测试环境运行项目:

mvn spring-boot:run -Dspring.profiles.active=test

#生产环境通过jar包运行项目:

java -jar kq.jar -Dspring.profiles.active=prod -Dspring.datasource.username=secret ...

#生产环境部署在tomcat下,在setenv.sh中配置参数:

#tomcat/bin/setenv.sh:

JAVA_OPTS="${JAVA_OPTS} -Dspring.profiles.active=prod"

JAVA_OPTS="${JAVA_OPTS} -Dspring.datasource.username=secret"

JAVA_OPTS="${JAVA_OPTS} -Dspring.datasource.password=secret"

...

 

2. H2+Postgresql+Flyway

    H2在开发环境和一些小项目中,是一个非常好的选择,尤其是其内存或文件模式由于没有网络开销,启动、运行快得一塌糊涂。但是当数据量达到百万级时,其性能就明显不如RDBMS头部的几大C/S数据库了。

    由于H2和Postgresql对SQL标准的良好兼容性,从H2切换到Postgresql并不是难事,这使我们在同一个项目中享受H2+Flyway带来的极速开发模式,和Postgresql的稳定和大数据量的支持并不冲突,同样配合Spring-Boot我们可以这样配置:

#application.properties:

#主配置文件中配置好所有默认参数

spring.profiles.active=dev

spring.datasource.initialize=false

flyway.baseline-version=0.0.0

flyway.baseline-on-migrate=true

flyway.validate-on-migrate=false

#application-dev.properties:

#开发环境使用内存模式,支持一键运行

spring.datasource.driver-class-name=org.h2.Driver

spring.datasource.url=jdbc:h2:mem:mydb;MODEL=;MODE=PostgreSQL

#application-sit.properties:

#自动化测试环境使用内存模式,支持一键运行

spring.datasource.driver-class-name=org.h2.Driver

spring.datasource.url=jdbc:h2:mem:mydb;MODEL=;MODE=PostgreSQL

#application-uat.properties:

#用户模拟测试使用postgresql数据库,保证代码与postgresql的兼容性

spring.datasource.driver-class-name=org.postgresql.Driver

spring.datasource.url=jdbc:postgresql://localsrv:5432/mydb

#保密要求高时,请使用JAVA虚拟机参数配置账号密码,如: -Dspring.datasource.username=test

spring.datasource.username=test

spring.datasource.password=123456

#application-prod.properties:

#生产环境建议所有数据库参数都使用JAVA虚拟机参数配置

spring.datasource.driver-class-name=org.postgresql.Driver

spring.datasource.url=jdbc:postgresql://localsrv:5432/mydb

#尤其是账号密码,一定不要写死在配置文件中

#开发环境运行项目:

mvn spring-boot:run

#自动化测试环境运行项目:

mvn spring-boot:run -Dspring.profiles.active=sit

#用户模拟测试环境运行项目:

mvn spring-boot:run -Dspring.profiles.active=uat

#生产环境通过jar包运行项目:

java -jar kq.jar -Dspring.profiles.active=prod -Dspring.datasource.username=secret ...

#生产环境部署在tomcat下,在setenv.sh中配置参数:

#tomcat/bin/setenv.sh:

JAVA_OPTS="${JAVA_OPTS} -Dspring.profiles.active=prod"

JAVA_OPTS="${JAVA_OPTS} -Dspring.datasource.username=secret"

JAVA_OPTS="${JAVA_OPTS} -Dspring.datasource.password=secret"

...

需要注意的是,存储过程、非SQL标准的类型、函数二者还是有差异。如果没用到这些,可以不改一行代码在H2和Postgresql之间平滑切换;如果用到了特殊的函数,可以通过Java来扩展H2函数来保证SQL与Postgresql一致;存储过程和特殊类型,就要大家自己去研究了。

3. Docker+Mysql/Postgresql+Flyway

    自从六年前用上Docker,我就喜欢上它一句命令就运行/停止/重置/"卸载"一套开源软件(如数据库、Web服务)的能力。通过Docker可以不留痕迹地尝试或使用绝大部分开源的新东西而不需要安装/卸载它们,加上现在Docker对Windows、Mac、Linux的全面支持,现在要用什么软件我首先会去找有没有现成的Docker镜像。

    使用Docker+Flyway来做持续集成(数据库的反复重建),也是一个非常好的选择,Docker配置一两句脚本,就可以达到安装或重置数据库的效果:

#删除已经存在的数据库容器,即使不存在也没什么影响

docker rm mydb

#启动数据库容器

docker run --name=mydb

-p 5432:5432

-d --restart=unless-stopped

-e POSTGRES_USER=${dbuser}

-e POSTGRES_PASSWORD=${dbpwd}

-e POSTGRES_DB=${database}

postgres:alpine

使用Docker还带来另一个好处——开发、测试、生产环境使用相同的镜像,则可以保证三者环境的一致,不太会遇到环境相关的问题。

 

使用Flyway需要考虑的问题

当然,想要用好Flyway,有些问题也需要提前考虑:

  • 如何在现有项目上使用Flyway

    我也是从项目开始一段时间之后,才开始使用Flyway的,上线Flyway的时候并没有遇到比手工执行脚本更困难的事情。现有项目想要使用Flyway,可以遵循以下三步:

  1. 导出现有数据库的建表脚本,和新项目必须的基础数据,放入Flyway升级脚本中,如:src/main/resources/db/migration/V0.0.0__init.sql
  2. 根据项目实际情况,按前述方法引入Flyway;
  3. 在Flyway配置中,在调用migrate之前,设置baselineVersion为一个大于步骤1中版本的值,如:V0.0.1。

baselineVerion参数设置方法,同样分Java代码、Spring Xml配置、Spring-Boot配置文件三种,看到这里你一定有能力自己去设置,我就不在复述。

  • 升级脚本越来越多怎么办

当经历几十个版本之后,"src/main/resources/db/migration"下面脚本越来越多,带来两个问题:

  1. 脚本太多,难以维护;
  2. 通过SQL脚本无法直观地看出最新的表结构。

我的经验和建议是:

  1. 关闭Flyway的脚本文件校验和检查,即设置validateOnMigrate=false;
  2. 定期将合并历史脚本,比如将V1.0.1,V1.0.2,V1.0.3的脚本都合并到V1.0中,并删除前三个文件。

脚本合并非Flyway才需要的技能,我有一些技巧让SQL更简洁:

  1. 将ALTER语句合并到CREATE语句中;
  2. DROP与CTEATE+ALTER抵消;
  3. UPDATE视情况合并到INSERT语句中(基础数据升级),或者直接去掉(业务数据升级)。

结语

    如果你在用关系数据库,赶紧把Flyway用起来,开始你的极速编码体验吧!

    如果你因为RDBMS数据升级问题而切换到NoSQL,那赶紧换机会切回RDBMS,用Flyway来管理升级吧!要不然更多线上Bug正等着你!

参考资料

  • Flyway官方文档: https://flywaydb.org/getstarted/how
  • 实际案例:一键部署极简考勤系统 https://gitee.com/chentao106/kq

 

以上是 被关系数据库建表和升级折磨?因为你没用大道至简的Flyway 的全部内容, 来源链接: utcz.com/z/513971.html

回到顶部