Spring Boot中两个数据库迁移工具Liquibase和Flyway的比较 - 4lex


当您需要使用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是一个强大的工具。选择在开发中使用这两种工具中的任何一种都可以使您获得良好的生产率。