被关系数据库建表和升级折磨?因为你没用大道至简的Flyway - Go语言中文社区

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


六年前Flyway已经是我TDD开发、持续集成工具栈中的重要一环了,作为早期用户,我早就应该为它做个”广告“,可惜对创业者来说时间太宝贵了,现在趁着疫情才有机会在家里总结点东西。

虽然现在Flyway已经是Spring-Boot集成工具的一环,但是我发现还是少有人了解它的威力。

关系数据库之殇

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

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

开发正调试着,忽然代码报错“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就是一个数据库版本管理组件。它的原理非常简单:

file

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

你没看错,以上三点就是Flyway最核心的功能,对非JVM开发者我推荐在理解以上思想的基本上自己开发一套。

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

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

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

快速上手

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

file

注:所有示例都基于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升级脚本:

file

被关系数据库建表和升级折磨?因为你没用大道至简的Flyway 生成的脚本名称如下:

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,有些问题也需要提前考虑:

1、如何在现有项目上使用Flyway

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

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

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

2、升级脚本越来越多怎么办

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

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

我的经验和建议是:

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

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

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

结语

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

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

文源网络,仅供学习之用,如有侵权,联系删除。

我将面试题和答案都整理成了PDF文档,还有一套学习资料,涵盖Java虚拟机、spring框架、Java线程、数据结构、设计模式等等,但不仅限于此。

关注公众号【java圈子】获取资料,还有优质文章每日送达。

file

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qianlia/article/details/105786813
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-06-27 18:47:47
  • 阅读 ( 1110 )
  • 分类:数据库

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢