被關係數據庫建表和升級折磨?由於你沒用大道至簡的Flyway

前言

    六年前Flyway已是我TDD開發、持續集成工具棧中的重要一環了,做爲早期用戶,我早就應該爲它作個」廣告「,惋惜對創業者來講時間太寶貴了,如今趁着疫情纔有機會在家裏總結點東西。雖然如今Flyway已是Spring-Boot集成工具的一環,可是我發現仍是少有人瞭解它的威力。java

關係數據庫之殤

    你在使用關係數據庫的過程當中,是否曾經遇到如下狀況,甚至所以一度想要放棄或已經放棄關係數據庫?git

場景一:開發環境,多人共用一套數據庫

開發正調試着,突然代碼報錯「XX字段不存在」:誰TMD又把表結構給改了…

場景二:開發環境,每一個人各自搭建本身的數據庫

開發完一個功能,提交代碼、更新,重啓準備調試下,代碼報錯「XX表不存在」
吼一嗓子:誰又改表結構了?什麼?每一個人都要把xxx.sql執行一遍?
...
新員工:我要搭一套開發數據庫,到底應該執行哪些SQL腳本?

場景三:開發轉測試

測試:你看這個功能是否是有個Bug?
開發1:哦,你要執行一下這個SQL腳本。
測試:嗯,如今沒問題了,可是怎麼保證這個腳本沒有Bug,我能再重現、測試一遍嗎?
開發:額~,你從新搭一遍數據庫吧...

場景四:搭建一套演示環境

執行SQL腳本一、SQL腳本二、SQL腳本3…啓動服務失敗! 什麼?這個腳本N是測試版本的,war包是已經上線的版本? 刪庫再來一遍...

場景五:放棄關係數據庫的坑

受不了關係數據庫了,咱們切MongoDB吧…嗯,控制檯果真清靜了 ...
幾個版本後: 生產環境某些數據查不到了、還有類型不匹配,神馬? A字段改過名,B字段換了個類型?

    若是上面的問題,你一個都沒遇到過,要麼是你所作的項目太簡單,要麼大家的開發流程很是規範,或者變動控制得太好了,本文你大可跳過了。spring

    若是你正被上面的問題困擾,你可能會所以想要入另外一個坑:NoSQL,那麼恭喜你將遇到更多的坑,好比關聯查詢問題、數據版本問題...sql

注:這裏並不否認NoSQL的價值,各類NoSQL是關係數據庫的良好補充。
可是若是想將NoSQL作爲關係數據庫的替代,那麼你將會陷入比關係數據庫還多的線上問題之中。

    若是你看到這裏,說明你不想逃避問題,那麼讓咱們一塊兒來認識這個關係數據庫升級管理的利器——Flywaydocker

 

Flyway原理介紹

Flyway是什麼?一句話歸納,Flyway就是一個數據庫版本管理組件。它的原理很是簡單數據庫

  1. 項目啓動時拉起Flyway,先檢查數據庫裏面有沒有Flyway元數據表,沒有則建立;
  2. 檢查Flyway元數據表中的記錄,哪些腳本已經執行過,當前版本是什麼;
  3. 查找代碼中的(名稱知足規則的)數據庫升級腳本,找出版本號大於(Flyway元數據)當前版本的腳本,逐個執行並記錄執行結果到Flyway元數據表。

    你沒看錯,以上三點就是Flyway最核心的功能,我深信熟練掌握我另外一篇博客《TDD兩小時實現自定義表達式模板解析器》同窗不出一天,就能本身實現這三點功能,對非JVM開發者我推薦在理解以上思想的基本上本身開發一套。tomcat

    大道至簡,最簡單的設計每每是最有效的。經過以上功能,咱們能夠很容易作到:bash

  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正等着你!

參考資料

相關文章
相關標籤/搜索