1.背景说明
一般应用/平台建设过程中,都会碰到一个研发团队一个应用/平台多个项目交付场景。存在多个产品版本还是一个版本管控的问题。按项目实现多个版本管控就会存在一个团队多项目协同、产品功能无法复用,功能重复建设,人员无法复用问题;按一个版本管控就会存在多项目本地化需求导致版本管控复杂,以及给跨版本升级带来的复杂管理问题,其中版本中的数据库脚本由于缺少版本管控工具,在跨版本升级中的管控难度尤为凸显。
2.问题分析
当研发团队选择多项目使用master来统一版本管理,在多个项目协同管理情况下,随着产品版本迭代,产生越来越多的版本,跨版本升级带来的脚本管控难度越来越大,升级操作复杂度越来越高,同时脚本执行导致数据缺失或者报错情况越来越多。分析的常见问题如下:
- 脚本无法版本化管控:跨版本升级(7.1升级到1.7.5),版本中心收录的增量脚本每次都需要手工整理。
- 定义项目脚本管理混乱:由于主干版本(master)和各个项目的分支(项目分支一般都加系统参数或开关实现),导致某些参数只能在项目分支版本执行,管理复杂。
- 脚本重复执行:7.1版本的脚本(项目A需要),但需要合并到1.7.2版本中,但是1.7.2版本其实已经发布到主干生产环境了,此时只能把脚本合到1.7.3版本中,等项目A要发版到1.7.3版本时,该版本的脚本就存在重复执行了。
- 不同的开发人员在开发产品特性时,都有可能更新数据库(添加新表,新的约束等)。当开发人员完成工作并提交代码时,代码会被合并到主分支并在测试服务器上执行单元测试与集成测试。我们在哪个环节来执行数据库的更新操作呢?由 QA部门手工执行 sql 脚本?或者我们开发一断程序自动执行数据库更新?以什么顺序来执行这些更新脚本?这些问题同样存在于生产环境。
- 我们的产品部署在不同的客户服务器上,以及很多的测试、联调、实验局、销售环境上。不同的客户和测试环境上都部署着不同版本的产品。当他们需要升级他们的产品到新的版本时,我们不仅需要让他们的管理员可以升级产品到新的版本,同时需要保留他们的已有数据。在升级产品的步骤中,我们清楚地知道客户数据库的当前版本,以及需要在该数据库上执行哪些数据库更新脚本,来更新数据库表结构与数据库中已存在的数据。当升级完成时,数据库表结构及数据应当与升级后的产品版本保持一致。
- 有的时候,我们需要通过代码(Java)来维护一些已存在的数据,如通过代码来维护 blob 类型的用户头像数据。
7.当升级失败时(比如在升级过程中出现网络连接失败),我们应当支持对失败进行修复。
3.解决思路
3.1 总体思路
借助flyWay开源工具实现以上sql脚本的多项目版本控制
3.2 方案说明
一、关键概念
- Schema History Table
Flyway 对数据库进行版本控制的方式,是在指定数据库中创建一张表,即 Schema History Table(默认为 flyway_schema_history),记录由 Flyway 所执行的 sql 脚本状态。
表中字段解释:
- installed_rank:执行序号
- version:版本
- script:sql脚本文件
- checksum:类似文件md5值,用来检查 migration 在执行过后是否发生了变化,如果发生了变化,会导致这个版本以及后续版本的 migration 无法执行
- success:是否执行成功,1成功,0失败
- Migration
Flyway 将每一个数据库脚本称之为:migration,migration 可以是 SQL 文件,也可以是 Java 类,默认的查找 migration 的路径为 classpath:db/migration,对应 SQL 文件可放置在 src/main/resources/db/migration 下,Java 类可放置在 src/main/java/db/migration 下。flyway 支持三种类型的migration:
- Versioned migrations:最常用的 migration,可以简单的理解为数据库升级脚本
- Undo migrations:数据库版本回退脚本,可为对应版本的常规 versioned migration 进行回退操作,此功能为收费功能,社区版无法使用
- Repeatable migrations:可重复执行的 migration,例如create or replace脚本,会在所有的 versioned migration 都执行过后再进行执行,即当脚本 checksum 改变时重新执行
所有的 migration 都需要遵守命名规范:
3、Versioned migrations
这里单独介绍下 Versioned migrations,关于命名规范,需要一个大写的 V 作为版本的前缀标志,然后在后面紧跟着一个数字作为版本号(版本号可以是数字加.的形式),这个就是 Flyway 进行追踪的依据,在版本号后面需要下划线作为分隔符用来分割版本号和说明(这里特别需要注意的是分隔符是两个下划线)。
其中版本号必须全局(一个 Schema History Table 里)唯一,且默认情况下(可通过参数调整)版本号只能增加,不能在已经执行了高版本的 migration 之后再执行低版本的 migration。
- flyway集成到springboot
1.pom文件中引入依赖
2.application.yml配置
3.sql脚本编写
在 src/main/resources 下新建 /db/migration 文件夹,并创建 sql 脚本文件:
上面的配置已经设置了 baseline-on-migrate 为 true(默认 false),即已经告诉 Flyway 执行 migration 的时候是存在基线的,这样就不会报出数据库表不存在的错了。因此不管你的数据库是否非空,都能顺利运行,唯一的区别是第一个 sql 脚本文件的执行。
拿上面这张图的 V1.0__init_database.sql 脚本来说,如果数据库为空,则默认会执行;反之则会把它作为基线,跳过执行下一个版本
- 初始化数据库
SpringBoot 集成完 Flyway 之后,数据库的创建同样需要作为程序启动即执行,否则项目复用时仍然需要先创建数据库再启动程序,不是很智能,部分代码如下:
在抽象初始化数据库这部分代码作为组件时,有一点需要注意,如果你的项目有使用 Mybatis-plus 框架,不建议采用自动配置的方式初始化bean,会出现 mp 的自动配置类执行顺序始终比自定义配置类快,可以采用 InitializingBean 和 BeanPostProcessor
- 注意事项
1、脚本文件如果已经执行过,便不要在进行任何更改,如果需要修改请通过新增一个新的 sql 脚本文件来达成;否则修改一个已经执行过的脚本,会导致 Flyway 校验失败(还原该脚本可以解决)
2、sql 脚本有错误,执行失败后进行修改,再次执行报错,理由同1,此时需要删除 flyway_schema_history 中对应版本的约束然后重新执行
3、增量脚本里的 sql 尽量保证可以多次执行