Skip to content

Latest commit

 

History

History
640 lines (498 loc) · 21 KB

liquibase.md

File metadata and controls

640 lines (498 loc) · 21 KB

Liquibase

About Liquibase

Liquibase是一个用于跟踪、管理和应用数据库变化的开源的数据库重构工具;它将所有数据库的变化(包括结构和数据)都保存在文件中(见附录2),便于版本控制。

Liquibase 具备如下特性:

  • 不依赖于特定的数据库
    • supported databases (见附录1)
  • 提供数据库比较功能
    • 比较结果保存在XML中,基于该XML你可用Liquibase轻松部署或升级数据库
  • 多人协作
    • 以XML存储数据库变化,其中以作者和ID唯一标识一个变化(ChangSet),支持数据库变化的合并,因此支持多开发人员同时工作
  • 历史版本留痕
    • 在数据库中保存数据库修改历史(DatabaseChangeHistory),在数据库升级时自动跳过已应用的变化 (ChangSet)
  • 回滚
    • 提供变化应用的回滚功能,可按时间、数量或**标签(tag)**回滚已应用的变化。通过这种方式,开发人员可轻易的还原数据库在任何时间点的状态
  • 生成变化文档
    • 可生成数据库修改文档(HTML格式) (见Liquibase Command 章节

* How to used?

第一步 导入依赖

Gradle importbuild.gradle

{% code-tabs %} {% code-tabs-item title="build.gradle" %}

compile("org.liquibase:liquibase-core")

{% endcode-tabs-item %} {% endcode-tabs %}

Maven importpom.xml

{% code-tabs %} {% code-tabs-item title="pom.xml" %}

<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
</dependency>

{% endcode-tabs-item %} {% endcode-tabs %}

第二步 配置springboot配置文件

Configuration application.yml

  • 需要在 application.yml中,增加liquibase配置

{% code-tabs %} {% code-tabs-item title="application.yml" %}

liquibase:
  change-log: classpath:config/liquibase/db.changelog.xml
  drop-first: false      #优先drop整库之后,顺序执行 master.xml
  enabled: true          #开启Liquibase
  check-change-log-location: true

{% endcode-tabs-item %} {% endcode-tabs %}

第三步 配置Liquibase部署文件

Configuration db.changelog.xml

  • db.changelog.xml中,配置初始化变化集changeSet、Tag标签、重大升级变化集changeSet等。

{% code-tabs %} {% code-tabs-item title="db.changelog.xml" %}

<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
    <property name="now" value="now()" dbms="h2"/>
    <property name="now" value="now()" dbms="mysql"/>
    <property name="autoIncrement" value="true"/>
    <property name="floatType" value="float4" dbms="postgresql, h2"/>
    <property name="floatType" value="float" dbms="mysql, oracle, mssql"/>

    <!-- 初始化的 变化集-->
    <changeSet id="20180628072451-1" author="suixingpay"> 
        <!-- 详见 【Configuration ChangeSet】 -->
    </changeSet>
    <!-- Tag标签分割,用于rollback到此-->
    <changeSet author="suixingpay" id="tag_1">
        <tagDatabase tag="version_0.1.0" />
    </changeSet>
    <!-- 重大升级 变化集合 etd. -->
</databaseChangeLog>

{% endcode-tabs-item %} {% endcode-tabs %}

* 如何版本管理

通过TAG管理版本

怎么打TAG?

  • changeSet中,打版本标签<tagDatabase tag="version_0.7.0" />如下:
      <changeSet author="suixingpay" id="1">
      <changeSet author="suixingpay" id="tag_version_0_7_0">
          <tagDatabase tag="version_0.7.0" />
      </changeSet>
      <changeSet author="suixingpay" id="2">
  • 每一个tagDatabase标签需要放在独立的changeSet中

TAG是怎么保证顺序的?

  • TAG顺序是按照classpath:config/liquibase/db.changelog.xml文件顺序
  • tag允许被字符串命名,建议与release number保持一致
  • rollback时,会回滚到指定tag之前的样子

版本升级

准备好即将升级的changeset脚本,详见【制作数据升级包

  • 手动升级
    • Gradle commands 执行此命令可以达到增量滚动升级脚本的目的

      gradle update
    • Maven commands 执行此命令可以达到增量滚动升级脚本的目的

      mvn update
  • 自动升级
    • 启动工程后,Liquibase会自动运行比较后,执行那些新增的changeSet,随后即可查看目标数据结构变化
      • 注意 此处 关闭 drop-first 避免业务销毁数据

        {% code-tabs %} {% code-tabs-item title="application.yml" %}

        drop-first: false      #优先drop整库之后,顺序执行 master.xml

        {% endcode-tabs-item %} {% endcode-tabs %}

  • 指定升级

版本回滚

  • Gradle commands 回滚到指定历史版本<tagDatabase tag="version_0.5.0" />

    gradle rollback -PliquibaseCommandValue=version_0.5.0
  • Maven commands 回滚到指定历史版本<tagDatabase tag="version_0.5.0" />

    mvn liquibase:rollback -Dliquibase.rollbackTag=version_0.5.0

* Configuration ChangeSet

ChangeSet

ChangeSet 存储每个阶段变化的集合,即变更集;它有以下几个主要属性:

  • id :唯一标示,不得重复
  • runOnChange : 当changeSet内容发生改变时,执行此
  • runAlways :总是执行此changeSet

ChangeSet Supported

ChangeSet 下的功能 Description e.g.
createTable 创建表
dropTable 销毁表
addColumn 加一列
modifyDataType 修改列
mergeColumns 合并列
dropColumn 销毁列
insert 插入数据
delete 删除数据
createIndex 创建索引
dropIndex 销毁索引

在changeSet下有一个非常重要的标签,rollback;它定义了回滚语句。

我要创建表结构

{% code-tabs %} {% code-tabs-item title="20180628072446_added_entity.xml" %}

    <!--
        Added the entity custom.
    -->
    <changeSet id="20180628072446-1" author="suixingpay">
        <createTable tableName="custom">
            <column name="id" type="bigint" autoIncrement="${autoIncrement}">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="username" type="varchar(11)" remarks="用户姓名">
                <constraints nullable="false" />
            </column>

            <column name="password" type="varchar(64)"  remarks="用户密码">
                <constraints nullable="false" />
            </column>

            <column name="mobile" type="varchar(11)"    remarks="手机号">
                <constraints nullable="false" />
            </column>

            <column name="password_strength" type="integer" remarks="密码强壮程度">
                <constraints nullable="false" />
            </column>

            <column name="nick_name" type="varchar(32)" remarks="昵称">
                <constraints nullable="true" />
            </column>

            <column name="full_name" type="varchar(32)" remarks="姓名">
                <constraints nullable="true" />
            </column>

            <column name="email" type="varchar(64)" remarks="邮箱">
                <constraints nullable="true" />
            </column>

            <column name="id_type" type="integer" remarks="证件类型">
                <constraints nullable="true" />
            </column>

            <column name="id_card" type="varchar(255)" remarks="证件号码">
                <constraints nullable="true" />
            </column>
            <column name="active" type="boolean" defaultValueBoolean="true" remarks="激活"/>
            <column name="bbbb" type="boolean" defaultValueBoolean="true" remarks="激活1"/>
            <column name="register_time" type="datetime" defaultValueComputed="${now}">
                <constraints nullable="false" />
            </column>

            <!-- needle-liquibase-add-column - we will add columns here, do not remove-->
        </createTable>
        <!-- 此处不写 rollback ,因为它可以被  autoRollback -->
        <!-- 【insert】 【drop】 【modifyDataType】必须写rollback,才会被回滚 -->
    </changeSet>

    <!-- Tag标签分割,用于rollback到此-->
    <changeSet author="suixingpay" id="tag_version_0_1_0">
        <tagDatabase tag="version_0.1.0" />
    </changeSet>

{% endcode-tabs-item %} {% endcode-tabs %}

我要建索引

{% code-tabs %} {% code-tabs-item title="20180628072450_added_index.xml" %}

   <!--
        modify mobile of the entity custom   length = 11 + 2 = 13.
    -->
    <changeSet id="201806280724549-1" author="suixingpay">
        <createIndex tableName="custom" indexName="idx_custom_username_mobile_nickname_fullname_registerxtime" unique="false">
            <column name="username"/>
            <column name="mobile"/>
            <column name="nick_name"/>
            <column name="full_name"/>
            <column name="register_time"/>
        </createIndex>
    </changeSet>

    <!-- Tag标签分割,用于rollback到此-->
    <changeSet author="suixingpay" id="tag_version_0_2_0">
        <tagDatabase tag="version_0.2.0" />
    </changeSet>

{% endcode-tabs-item %} {% endcode-tabs %}

如何初始化数据

{% code-tabs %} {% code-tabs-item title="20180628072449_insert_data.xml" %}

    <changeSet id="20180628072450-1" author="suixingpay" >
        <insert tableName="custom">
            <column name="username" value="liyunlong"/>
            <column name="password" value="123456"/>
            <column name="mobile" value="18651698213"/>
            <column name="password_strength" value="1"/>
            <column name="nick_name" value="昵称"/>
            <column name="full_name" value="姓名"/>
            <!-- <column name="status" value="1"/> -->
        </insert>
        <!-- 回滚语句 -->
        <rollback>
            <delete tableName="custom">
                <where>
                    username = 'liyunlong'
                </where>
            </delete>
        </rollback>
    </changeSet>

    <!-- Tag标签分割,用于rollback到此-->
    <changeSet author="suixingpay" id="tag_version_0_3_0">
        <tagDatabase tag="version_0.3.0" />
    </changeSet>

{% endcode-tabs-item %} {% endcode-tabs %}

制作数据库升级包

  • addColumn 增加列
  • modifyDataType 修改列的数据类型或长度等
  • dropColumn 删除列定义
    <!--
        Added column of the entity custom .
    -->
    <changeSet id="20180628072447-1" author="suixingpay">
        <addColumn tableName="custom">
            <column name="status" type="tinyint">
                <constraints nullable="false"></constraints>
            </column>
        </addColumn>
    </changeSet>

    <!--
        modify mobile of the entity custom   length = 11 + 2 = 13.
    -->
    <changeSet id="20180628072448-1" author="suixingpay">
        <modifyDataType tableName="custom" columnName="mobile" newDataType="varchar(13)"/>
        <rollback>
            <modifyDataType tableName="custom" columnName="mobile" newDataType="varchar(11)"/>
        </rollback>
    </changeSet>

    <!--
        drop bbbb of the entity custom
    -->
    <changeSet id="20180628072451-1" author="suixingpay">
        <dropColumn tableName="custom" columnName="bbbb" />
        <rollback>
            <addColumn tableName="custom">
                <column name="bbbb" type="boolean" defaultValueBoolean="true" remarks="测试"></column>
            </addColumn>
        </rollback>
    </changeSet>

    <changeSet id="20180628072452-1" author="suixingpay">
        <dropColumn tableName="custom" columnName="active" />
        <rollback>
            <addColumn tableName="custom">
                <column name="active" type="boolean" defaultValueBoolean="true" remarks="激活1"></column>
            </addColumn>
        </rollback>
    </changeSet>

    <changeSet author="suixingpay" id="tag_version_0_4_0">
        <tagDatabase tag="version_0.4.0" />
    </changeSet>

完整的 db.changelog.xml

{% file src="../../.gitbook/assets/db.changelog_now.xml" %}

Advanced Usage

Preconditions 条件判断

  • 简单判断
    <changeSet id="1" author="bob">
        <preConditions onFail="WARN">
            <sqlCheck expectedResult="0">select count(*) from oldtable</sqlCheck>
        </preConditions>
        <comment>Comments should go after preCondition. If they are before then liquibase usually gives error.</comment>
        <dropTable tableName="oldtable"/>
    </changeSet>
  • Will require running on Oracle OR MySQL which makes more sense than the above example.
 <preConditions>
     <or>
         <and>
            <dbms type="oracle" />
            <runningAs username="SYSTEM" />
         </and>
         <and>
            <dbms type="mysql" />
            <runningAs username="root" />
         </and>
     </or>
 </preConditions>

Parameters 应用

  • oracle 和 mysql 在一些 type 命名是不一样的,可以通过此种方式做通用适配
Attribute Description
name Name of the table's schema required
value Name of the column's table required
context Contexts given as comma separated list.
dbms Database types given as comma separated list.
    <property name="clob.type" value="clob" dbms="oracle"/>
    <property name="clob.type" value="longtext" dbms="mysql"/>

    <changeSet id="1" author="joe">
         <createTable tableName="${table.name}">
             <column name="id" type="int"/>
             <column name="${column1.name}" type="${clob.type}"/>
             <column name="${column2.name}" type="int"/>
         </createTable>
    </changeSet>

Database “Diff”

http://www.liquibase.org/documentation/diff.html

Liquibase Command

反向生成 Liquibase from database

  • 将已有数据库结构生成liquibase.xml

{% code-tabs %} {% code-tabs-item title="generate.sh" %}

./liquibase --driver=com.mysql.jdbc.Driver \  
                --classpath=lib/ \
                --changeLogFile=config/liquibase/changelog/db.changelog.xml \
                --url="jdbc:mysql://172.16.60.247:3306/suixingpay-starter-demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull" \
                --username=fd \
                --password=123456 \
                generateChangeLog

{% endcode-tabs-item %} {% endcode-tabs %}

生成变更文档

  • 将变更记录生成文档
./liquibase --driver=com.mysql.jdbc.Driver \  
                --classpath=lib/ \
                --changeLogFile=config/liquibase/changelog/db.changelog.xml \
                --url="jdbc:mysql://172.16.60.247:3306/suixingpay-starter-demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull" \
                --username=fd \
                --password=123456 \
                dbDoc \
                docs/dbdoc/
  • 图示

比较 Diff

  • 比较两个数据库差异
./liquibase --driver=com.mysql.jdbc.Driver 
                --classpath=lib/ 
                --changeLogFile=config/liquibase/changelog/db.changelog.xml 
                --url="jdbc:mysql://172.16.60.247:3306/suixingpay-starter-demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull" 
                --username=fd --password=123456 
                diff 
                --referenceUrl="jdbc:mysql://172.16.60.247:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull" 
                --referenceUsername=fd 
                --referencePassword=123456
  • 结果如下图所示,红框内为变化量:

More Command

http://www.liquibase.org/documentation/command_line.html

附录

1.Supported Databases

Database Type Name Notes
MySQL mysql No Issues
PostgreSQL postgresql 8.2+ is required to use the "drop all database objects" functionality.
Oracle oracle 11g driver is required when using the diff tool on databases running with AL32UTF8 or AL16UTF16
Sql Server mssql No Issues
Sybase_Enterprise sybase ASE 12.0+ required. "select into" database option needs to be set. Best driver is JTDS. Sybase does not support transactions for DDL so rollbacks will not work on failures. Foreign keys can not be dropped which can break the rollback or dropAll functionality.
Sybase_Anywhere asany Since 1.9
DB2 db2 No Issues. Will auto-call REORG when necessary.
Apache_Derby derby No Issues
HSQL hsqldb No Issues
H2 h2 No Issues
Informix informix No Issues
Firebird firebird No Issues
SQLite sqlite No Issues

2.Works with You

Configuration Gradle build.xml

liquibase {
    activities {
        main {
            changeLogFile 'src/main/resources/config/liquibase/changelog/db.changelog_now.xml'
            username 'fd'
            url 'jdbc:mysql://172.16.60.247:3306/suixingpay-starter-demo?userSSL=false'
            password '123456'
        }
        diff {
            changeLogFile 'src/main/resources/config/liquibase/changelog/*'
            url 'jdbc:mysql://172.16.60.247:3306/test'
            username 'fd'
            password '123456'
        }
    }
    runList = 'main'
}

Q&A

如何在Development、Test、CI&CD、Production等环境下,选择性使用Liquibase

只需要在@configuration 配置一套关于所在环境决定开启Liquibase即可,代码如下:

{% code-tabs %} {% code-tabs-item title="LiquibaseSimpleConfiguration.java" %}

@Configuration
public class LiquibaseSimpleConfiguration {
    private final Logger log = LoggerFactory.getLogger(LiquibaseSimpleConfiguration.class);
    private final Environment env;
    public LiquibaseSimpleConfiguration(Environment env) {
        this.env = env;
    }

    @Bean
    public SpringLiquibase liquibase(DataSource dataSource) {
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setDataSource(dataSource);
        liquibase.setChangeLog("classpath:config/liquibase/master.xml");
        liquibase.setDropFirst(false);  // 是否先 drop schema(默认 false)
        if (env.acceptsProfiles(SPRING_PROFILE_NO_LIQUIBASE)) { //判断环境变量
            liquibase.setShouldRun(false); //是否关闭Liquibase
        } else {
            liquibase.setShouldRun(true);
            log.debug("Configuring Liquibase");
        }
        return liquibase;
    }
}

{% endcode-tabs-item %} {% endcode-tabs %}

CI&CD 如果与生产环境不同该如何处理?

  1. 准备CI&CD环境配置,准备CI&CD数据库,以便与生产网络不通时,让CI&CD可以继续运行,不至于阻塞;
  2. 而到了生产部署时,liquibase.enabled: true 只需开启,便会自动执行滚动升级

TAG版本号如何管理?

  1. 版本号是字符串,建议根据release number设定,tagDatabase是按文件顺序切割版本,所以rollback时,注意changeset顺序即可。