Liquibase是开源的数据库表结构管理解决方案,可以轻管理表结构的变更。

众所周知,使用git管理代码可以使得多人协作、版本管理等工作变得异常轻松。但数据库表结构的管理却一直没有很好的解决方案,比如每次变更数据库都要手动执行SQL脚本,不同版本的程序和不同版本的数据库表结构要小心的匹配,跨数据库平台的还需为不同数据库准备不同SQL脚本,数据库表结构只能升级、不能降级。

参考资料

  • Liquibase官方文档
  • 支持的数据库

    Liquibase支持大部分数据库(如果不是所有的话),包括:mysql, postgresql, mariadb, mssql, oracle, db2, sybase, h2, derby, firebird, hsqldb, sqlite。

    参考资料

  • Liquibase Supported databases
  • 工作原理

    Liquibase使用一种通用的语言描述数据库的变更,代替传统的SQL语句,以实现上述目标。

    Liquibase允许使用SQL、XML、JSON、YAML文件格式。SpringBoot默认使用YAML文件格式,这里以YAML格式为例进行说明。

    如要为person表增加country列,通常SQL代码为: ALTER TABLE person ADD country varchar(2) ,在Liquibase中则描述为:

    - changeSet:
           id:  4
           author:  your.name
           changes:
            - addColumn:
                 tableName:  person
                 columns:
                   - column:
                       name:  country
                       type:  varchar(2)
    

    其中 id 是该changeSet的唯一标识, author 则标记该changeSet是谁编写的。

    Liquibase可以根据此描述,为不同数据库生成不同SQL代码。并且会在数据库中自动创建 DATABASECHANGELOG 表,用于记录哪些 changeSet 是执行过的。

    由此可知Liquibase的原理还是很简单的。

    参考资料

  • Getting Started with Liquibase and YAML on Windows
  • Liquibase语句

    只要是SQL支持的Liquibase绝大部分都支持。如createTable, addColumn, dropTable, dropColumn, renameColumn, modifyDataType, createIndex, addForeignKeyConstraint 等等。

    以下是createTable(创建表)的语句:

    changeSet:
      id:  createTable-example
      author:  liquibase-docs
      changes:
      -  createTable:
          columns:
          -  column:
              name:  address
              type:  varchar(255)
          tableName:  person
    

    详细语句请参考官方文档: Liquibase Change Types

    在SpringBoot中的使用

    SpringBoot自带了对Liquibase的支持,使用起来非常方便。只需要在 pom.xml 中增加如下配置:

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

    默认的yaml文件位于 src/main/resources/ db/changelog/db.changelog-master.yaml ,可以直接在这个文件上写入Liquibase代码。

    但最佳实践是将不同版本的changelog放在独立的文件。例如 1.0 版本的changelog为 db.changelog-1.0.yaml 2.0 版本的chagelog为 db.changelog-2.0.yaml 。在 db.changelog-master.yaml 将其包含进来。

    databaseChangeLog:
      - include:
          file: db.changelog-1.0.yaml
          relativeToChangelogFile: true
      - include:
          file: db.changelog-2.0.yaml
          relativeToChangelogFile: true
    

    完成SpringBoot的配置之后,程序启动时就会解析 db.changelog-master.yaml 文件的内容,并和Liquibase自动创建的 DATABASECHANGELOG 表的内容进行比对,没有执行的changelog会根据当前数据库类型生成相应的SQL自动执行,并写入 DATABASECHANGELOG

    需要注意的是,Liquibase并不会对表结构自动回滚。比如git从2.0版本切换到1.0版本,数据库结构不会自动回滚到1.0版本。因为数据库结构回滚可能会删表(表里的数据也就没了),是风险极大的操作。Liquebase支持数据库结构回滚,给不同版本的数据库结构做 tag 标记,可回滚到某个标记处。对于某些Liquibase无法自动生成回滚语句的(比如insert、update语句),需要自己在changelog编写回滚语句。这些是高级用法,且用的极少,这里就不多做介绍。

    在UJCMS项目中就使用了Liquibase作为数据库版本管理工具,可参考实例代码: UJCMS db.changelog-master.yaml

    参考资料

  • Execute Liquibase Database Migrations on Startup
  • 字段默认值

    Liquibase对字段默认值的处理非常值得注意,使用 defaultValue 的内容一律作为字符串处理,如果希望使用其它数据类型,应使用相应的属性。

  • defaultValue:字符串
  • defaultValueBoolean:布尔值
  • defaultValueNumeric:数值
  • defaultValueDate:日期。使用其中一种格式: YYYY-MM-DD , hh:mm:ss or YYYY-MM-DDThh:mm:ss
  • defaultValueComputed:表达式。特别的,使用 current_timestamp current_datetime 或者 current_timestamp(3) 可自动转化为相应数据库的日期函数,如MySQL的 now() now(3)
  • 参考资料:

  • liquibase attributes column
  • LiquibaseDataType#isCurrentDateTimeFunction
  • property的使用

    proerty可以支持数据库类型判断,如:

    <property name="now" value="sysdate" dbms="oracle"/>
    <property name="now" value="now()" dbms="mysql"/>
    <property name="now" value="now()" dbms="postgresql"/>
    <column name="join_date" defaultValueComputed="${now}"/>
    
  • 无法对MySQL单独增加字段注释,增加注释时,可能会丢失字段非空约束。修改字段类型、增加非空约束时,会丢失字段注释。所以不要纠结MySQL字段的注释问题。
  • 不要使用drop primary key,达梦不支持。应该先改表名、新建无约束新表(包括不要主键)、复制数据、删旧表、新表加约束。
  •