六年前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最核心的功能,我深信熟練掌握我另外一篇博客《TDD兩小時實現自定義表達式模板解析器》同窗不出一天,就能本身實現這三點功能,對非JVM開發者我推薦在理解以上思想的基本上本身開發一套。tomcat
大道至簡,最簡單的設計每每是最有效的。經過以上功能,咱們能夠很容易作到:bash
總之,用上Flyway以後,關係數據庫的」關係「,再也不是限制你開發效率的瓶頸,反而成爲開發&測試的必要約定,提高版本質量的重要保障。服務器
無論工具多強大,如何用起來,是咱們首要關心的,讓咱們以各類Java項目環境,來看一下如何在代碼中將Flyway用起來,再由各位本身去細品Flyway對關係數據庫版本管理帶來的巨大改變。網絡
注:全部示例都基於Maven,用Gradle的本身翻譯下依賴,兩者都不用的嘛...先研究下POM的依賴關係,再本身去下載jar包吧
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
注:腳本中的內容,就是正常的建表腳本,或者對上一版本的表結構變動、數據升級。
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
注:腳本中的內容,就是正常的建表腳本,或者對上一版本的表結構變動、數據升級。
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
爲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的做用
H2是一個純Java實現的相似Derby的數據庫,其最大的特色有三個:
以上三個特色,決定了它特別適合作持續集成,或者一鍵部署的項目:
若是配合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" ...
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,能夠遵循如下三步:
baselineVerion參數設置方法,一樣分Java代碼、Spring Xml配置、Spring-Boot配置文件三種,看到這裏你必定有能力本身去設置,我就不在複述。
當經歷幾十個版本以後,"src/main/resources/db/migration"下面腳本愈來愈多,帶來兩個問題:
個人經驗和建議是:
腳本合併不是Flyway才須要的技能,我有一些技巧讓SQL更簡潔:
若是你在用關係數據庫,趕忙把Flyway用起來,開始你的極速編碼體驗吧!
若是你由於RDBMS數據升級問題而切換到NoSQL,那趕忙換機會切回RDBMS,用Flyway來管理升級吧!要否則更多線上Bug正等着你!