Spring Boot中兩個資料庫遷移工具Liquibase和Flyway的比較 - 4lex

banq發表於2020-05-05

當您需要使用Java建立Web應用程式或API時,可以使用RESTful,SOAP或GraphQL。無論您是檢視同步HTTP,非同步還是反應式,佇列中的訊息或來自Kafka的事件,都很難超越Spring生態系統。

資料庫遷移

如果您使用的是Spring,則有可能使用諸如HibernateJooqEbean之類的永續性技術從資料庫中獲取資料。

功能來去去去發生變化;資料模型也發生變化,當您需要更改資料模型時會發生什麼?您執行資料庫遷移以新增或刪除列或進行其他更改。

過去通常是手動進行,有人會登入到資料庫,執行大量SQL,並依賴於您在應用程式中進行了相同的更改。雖然這裡還有很多人為錯誤的餘地。通過嘗試使資料庫更改更安全,我們對此進行了改進。版本控制,可重複性,可測試性;這些概念適用於堆疊的更多部分。包括資料庫。

Liquibase和Flyway都可以幫助我們進行這些遷移。Spring會依次幫助我們使用Liquibase和Flyway。結果是正確實現了以確保我們的資料庫處於所需狀態,我們要做的就是編寫一個遷移並將其轉儲到Spring專案的資料夾中。有了一些配置和更多的魔術,Spring將處理其餘的工作。

Liquibase

Liquibase是有免費提供和收費的。但是,他們沒有在其網站上分享價格,這使我感到懷疑。他們在描述我為什麼要付錢時做得並不出色。Liquibase通過XML和SQL提供遷移。基本概念是您擁有一個主檔案,該檔案描述了資料庫配置以及要包含在執行中的變更集。Spring進入了這一點,並解析您的配置和包含的變更集,並對其進行適當的管理。

Flyway 

Flyway與Liquibase一樣免費提供並收費。價格和功能明細可在此處獲得 -並且比我在liquibase上可以找到的任何東西都要詳細得多。

Flyway將您的遷移作為一流的概念。您編寫SQL指令碼,將它們放置在Spring專案的資料夾中,向檔案中新增一些配置application.yml,然後Spring按照配置執行遷移。

使用Liquibase

注意:我在這裡將Spring Boot 2.2.6與Gradle一起使用,但是您可以在Maven中使用依賴項塊進行等效操作。

第一步是將您的依賴項新增到build.gradle:

// Persistence
    implementation('com.h2database:h2')
    implementation "org.liquibase:liquibase-core"
    liquibaseRuntime "org.liquibase:liquibase-core"
    liquibaseRuntime sourceSets.main.compileClasspath
    liquibaseRuntime "org.postgresql:postgresql"
    liquibaseRuntime "com.h2database:h2"
    implementation('org.postgresql:postgresql')
    implementation('org.springframework.boot:spring-boot-starter-jdbc')

接下來,我們需要在專案中建立新資料夾:

  • 在src/main/resources新增liquibase資料夾
  • 新增兩個子資料夾,changelog和fake-data

在您的application.yml檔案中新增一些最小的配置,您就可以開始了。

liquibase:
    contexts: dev, faker
    change-log: classpath:liquibase/master.xml

此時,您應該能夠開始編寫一些XML。

您將需要一個,master.xml以便描述您的環境設定和要執行的遷移。這是一個例子:

<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">

    <property name="now" value="now()" dbms="h2"/>
    <property name="now" value="current_timestamp" dbms="postgresql"/>

    <property name="floatType" value="float4" dbms="postgresql, h2"/>
    <property name="clobType" value="longvarchar" dbms="h2"/>
    <property name="clobType" value="clob" dbms="postgresql"/>
    <property name="uuidType" value="uuid" dbms="h2, postgresql"/>

    <changeSet id="00000000000000" author="alex">
        <createSequence sequenceName="sequence_generator" startValue="1050" incrementBy="1"/>
    </changeSet>

    <include file="liquibase/changelog/20191024203226_added_entity_Company.xml" relativeToChangelogFile="false"/>
    <include file="liquibase/changelog/20191024203227_added_entity_Team.xml" relativeToChangelogFile="false"/>
    <include file="liquibase/changelog/20191024203234_added_entity_Project.xml" relativeToChangelogFile="false"/>
</databaseChangeLog>

看起來有點亂,但讓我們分解一下。首先,我們需要告訴Liquibase我們的資料庫設定,以及它對各種資料型別應使用的值

   

<property name="now" value="now()" dbms="h2"/>
    <property name="now" value="current_timestamp" dbms="postgresql"/>

    <property name="floatType" value="float4" dbms="postgresql, h2"/>
    <property name="clobType" value="longvarchar" dbms="h2"/>
    <property name="clobType" value="clob" dbms="postgresql"/>
    <property name="uuidType" value="uuid" dbms="h2, postgresql"/>

我們還需要描述我們希望它做出的改變。在我們的例子中,我們希望Liquibase建立一個序列並執行三個遷移。請注意,如果您變動了但不將其包括在中master.xml,則它們將不會執行。

    <changeSet id="00000000000000" author="alex">
        <createSequence sequenceName="sequence_generator" startValue="1050" incrementBy="1"/>
    </changeSet>

    <include file="liquibase/changelog/20191024203226_added_entity_Company.xml" relativeToChangelogFile="false"/>
    <include file="liquibase/changelog/20191024203227_added_entity_Team.xml" relativeToChangelogFile="false"/>
    <include file="liquibase/changelog/20191024203234_added_entity_Project.xml" relativeToChangelogFile="false"/>

這是公司遷移:

<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">

    <changeSet id="20191024203226-1" author="alex">
        <createTable tableName="company">
            <column name="id" type="bigint" autoIncrement="${autoIncrement}">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="name" type="varchar(255)">
                <constraints nullable="false"/>
            </column>
            <column name="web_page_address" type="varchar(255)">
                <constraints nullable="true"/>
            </column>
            <column name="billing_contact_email_address" type="varchar(255)">
                <constraints nullable="true"/>
            </column>
            <column name="primary_contact_email_address" type="varchar(255)">
                <constraints nullable="true"/>
            </column>

        </createTable>
    </changeSet>

    <changeSet id="20191024203226-1-data" author="alex" context="faker">
        <loadData
                file="liquibase/fake-data/company.csv"
                separator=";"
                tableName="company">
            <column name="id" type="numeric"/>
            <column name="name" type="string"/>
            <column name="web_page_address" type="string"/>
            <column name="billing_contact_email_address" type="string"/>
            <column name="primary_contact_email_address" type="string"/>
        </loadData>
    </changeSet>

</databaseChangeLog>

遷移的第一步是建立一個名為Company的表。由於Liquibase使用XML作為SQL之上的抽象,因此我們不必太擔心相容性,因此Liquibase將檢視XML檔案並將請求轉換為適當的SQL方言。

    <changeSet id="20191024203226-1" author="alex">
        <createTable tableName="company">
            <column name="id" type="bigint" autoIncrement="${autoIncrement}">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="name" type="varchar(255)">
                <constraints nullable="false"/>
            </column>
            <column name="web_page_address" type="varchar(255)">
                <constraints nullable="true"/>
            </column>
            <column name="billing_contact_email_address" type="varchar(255)">
                <constraints nullable="true"/>
            </column>
            <column name="primary_contact_email_address" type="varchar(255)">
                <constraints nullable="true"/>
            </column>

        </createTable>
    </changeSet>

定義好表格後,我們可以利用Liquibase可以提供的簡潔功能之一-Liquibase將從CSV將資料載入到我們的資料庫中:

    <changeSet id="20191024203226-1-data" author="alex" context="faker">
        <loadData
                file="liquibase/fake-data/company.csv"
                separator=";"
                tableName="company">
            <column name="id" type="numeric"/>
            <column name="name" type="string"/>
            <column name="web_page_address" type="string"/>
            <column name="billing_contact_email_address" type="string"/>
            <column name="primary_contact_email_address" type="string"/>
        </loadData>
    </changeSet>

資料載入本身就是一個變更集。這意味著您可以在建立表時或以後靈活地載入資料。如果您要建模的關係很複雜,以這種方式載入資料會很有用。這裡的另一個技巧是context="faker"上面的陳述。如果您要載入資料,請確保您application.yml的liquibase條目包含該標記。如果刪除該標籤,則不會載入資料。

這是上面引用的CSV,其中包含一些示例資料:

id,name,web_page_address,billing_contact_email_address,primary_contact_email_address
1,mobileFish,HomeLoanAccountTableComputer,Trace,SavingsAccountalarm
2,TastyMetalBacon,redChipsSoap,HomeLoanAccount,web-readiness

當您執行spring boot應用程式時,作為啟動的一部分,Liquibase將應用此遷移,並且當您的應用程式連線到資料庫時,表和資料將立即準備就緒。

liquibase如何知道資料庫憑證?我不確定!這是魔法。我假設它掛接到您的spring資料庫配置中,其示例可能是:

datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:
    hikari:
      poolName: Hikari
      auto-commit: false

更多見:使用Liquibase和Spring Boot進行資料庫遷移的一站式指南 - reflectoring

使用Flyway

Flyway對SQL的關注使事情變得井井有條。

首先,新增相關的依賴項:

// Persistence
    implementation('com.h2database:h2')
    implementation('org.postgresql:postgresql')
    implementation('org.springframework.boot:spring-boot-starter-jdbc')
    implementation "org.flywaydb:flyway-core"

告訴Flyway如何連線到資料庫(在我的情況下,通過application.yml):

  flyway:
    locations: classpath:db/migration/dev
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
    user: sa
    password:

再次在中建立一些資料夾src/main/resources:

  • 建立 db/migration
  • 就我而言,我在開發人員中使用H2,在測試和生產中使用PostgreSQL,所以我有兩個同級資料夾,dev並且prod(測試和生產指令碼都生活在生產中,因為我希望它們相同,並且它們是相同的SQL方言) 。

下一步是用適當的SQL方言編寫一些遷移。公司遷移以前是用XML進行的,而現在我們是SQL的,請注意,這裡有一個命名約定,使我們可以減少樣板。您必須像這樣命名檔案,以便V1.0__create_company.sqlVX.x定義執行遷移的順序。無論如何,這是檔案:

create sequence hibernate_sequence start with 1050;

create table company
(
    id                            uuid         not null,
    name                          varchar(255) not null,
    web_page_address              varchar(255),
    billing_contact_email_address varchar(255),
    primary_contact_email_address varchar(255),
    constraint pk_company
        primary key (id)
);

在我看來,刪除XML使事情變得更清晰易讀。我們也載入資料,但這只是一個SQL指令碼而不是CSV:

INSERT INTO PUBLIC.COMPANY (ID, NAME, WEB_PAGE_ADDRESS, BILLING_CONTACT_EMAIL_ADDRESS, PRIMARY_CONTACT_EMAIL_ADDRESS)
VALUES ('1a689e52-f35b-4bda-934c-ea4f076bdc2c', 'Blue Fish Software Inc', 'bluefish.io', 'bills@bluefish.io',
        'hi@bluefish.io');

當應用程式執行時,它將載入這些SQL指令碼併為我們管理遷移和狀態。這裡的缺點是我們需要兩套指令碼,一套用於H2,一套用於PostgreSQL-這可能是因為我的SQL非常脆弱,我可以將它們組合在一起但沒有意識到。

比較Liquibase和Flyway

儘管使用Liquibase的時間更長了,但我發現Flyway的使用更加容易。

我喜歡Flyway更乾淨,重複性更低,並且假設我解決了方言的問題,那麼與Liquibase中的相​​同設定相比,使用Flyway可以減少1000多行程式碼。我還發現Flyway提供的文件要好得多,而且我遇到的Flyway所需的技巧少得多。如果開始需要的關鍵資訊未儲存在文件頁面上,而是在堆疊溢位時儲存,則說明您做錯了什麼。Liquibase對此不利。

由於錯誤地將特定的UUID值檢測為字串,因此使用Liquibase無法在所有實體上從主鍵bigint移至uuid主鍵。

Flyway沒有此類問題,在我確實犯了Flyway錯誤的情況下,我得到了列印的SQL異常,而不是Liquibase提取的SQL異常。我與Flyway的摩擦少得多。

通過Reddit的u / hooba_stank指出了liquibase的配置檔案賦予它出色的靈活性。在不同的環境中使用上下文,針對不同的測試需求以及條件變更集和回滾來組合配置檔案,使免費的Liquibase產品引人注目。這是一個好點。

如果您希望每個檔案進行較小的更改,則Flyway對SQL檔案進行順序排序的要求可能會導致擴充套件。遇到失敗是因為您用SQL指令碼版本號胖了也很煩。u / koreth補充說,可以通過配置預提交掛鉤和CI檢查來檢測有衝突的序列號來避免此問題,因此可以在一定程度上實現此問題的自動化,儘管理想情況下您根本不必處理此問題。

總結

到目前為止,Liquibase和Flyway都比沒有強。我個人更喜歡Flyway。Flyway實施起來更清潔,更易於持續使用並且更具可讀性。話雖這麼說,Liquibase是一個強大的工具。選擇在開發中使用這兩種工具中的任何一種都可以使您獲得良好的生產率。

 

相關文章