• SpringBoot结合Liquibase实现数据库变更管理


    《从零打造项目》系列文章

    工具

    ORM框架选型

    数据库变更管理

    定时任务框架

    • 待更新

    缓存

    • 待更新

    安全框架

    • 待更新

    开发规范

    • 待更新

    前言

    在《SpringBoot项目基础设施搭建》一文中有提到过 liquibase,以及还自定义了一个 Maven 插件,可能大家当时看到这块内容,虽然好奇但不知道该如何使用。本文将带着大家实操一个 SpringBoot 结合 Liquibase 的项目,看看如何新增数据表、修改表字段、初始化数据等功能,顺带使用一下 Liquibase 模版生成器插件。

    如果对 Liquibase 不了解,可以先看一下我的上一篇文章《数据库变更管理:Liquibase or Flyway》。

    实操

    本项目包含两个小项目,一个是 liquibase 模版生成器插件,项目名叫做 liquibase-changelog-generate,另一个项目是 liquibase 应用,叫做 springboot-liquibase。

    Liquibase模版生成器插件

    创建一个 maven 项目 liquibase-changelog-generate,本项目具备生成 xml 和 yaml 两种格式的 changelog,个人觉得 yaml 格式的 changelog 可读性更高。

    1、导入依赖

    <dependencies>
      
      <dependency>
        <groupId>org.apache.mavengroupId>
        <artifactId>maven-plugin-apiartifactId>
        <version>3.8.6version>
      dependency>
      <dependency>
        <groupId>org.apache.maven.plugin-toolsgroupId>
        <artifactId>maven-plugin-annotationsartifactId>
        <version>3.6.4version>
        <scope>providedscope>
      dependency>
      <dependency>
        <groupId>cn.hutoolgroupId>
        <artifactId>hutool-allartifactId>
        <version>5.8.5version>
      dependency>
    
    dependencies>
    
    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.pluginsgroupId>
          <artifactId>maven-plugin-pluginartifactId>
          <version>3.6.4version>
          
          <configuration>
            <goalPrefix>hreshgoalPrefix>
            <skipErrorNoDescriptorsFound>trueskipErrorNoDescriptorsFound>
          configuration>
        plugin>
        <plugin>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-maven-pluginartifactId>
          <version>2.6.3version>
        plugin>
        
        <plugin>
          <groupId>org.apache.maven.pluginsgroupId>
          <artifactId>maven-compiler-pluginartifactId>
          <configuration>
            <source>1.8source>
            <target>1.8target>
          configuration>
        plugin>
      plugins>
    build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    2、定义一个接口,提前准备好公用代码,主要是判断 changelog id 是否有非法字符,并且生成 changelog name。

    public interface LiquibaseChangeLog {
    
      default String getChangeLogFileName(String sourceFolderPath) {
        System.out.println("> Please enter the id of this change:");
        Scanner scanner = new Scanner(System.in);
        String changeId = scanner.nextLine();
        if (StrUtil.isBlank(changeId)) {
          return null;
        }
    
        String changeIdPattern = "^[a-z][a-z0-9_]*$";
        Pattern pattern = Pattern.compile(changeIdPattern);
        Matcher matcher = pattern.matcher(changeId);
        if (!matcher.find()) {
          System.out.println("Change id should match " + changeIdPattern);
          return null;
        }
    
        if (isExistedChangeId(changeId, sourceFolderPath)) {
          System.out.println("Duplicate change id :" + changeId);
          return null;
        }
    
        Date now = new Date();
        String timestamp = DateUtil.format(now, "yyyyMMdd_HHmmss_SSS");
        return timestamp + "__" + changeId;
      }
    
      default boolean isExistedChangeId(String changeId, String sourceFolderPath) {
        File file = new File(sourceFolderPath);
        File[] files = file.listFiles();
        if (null == files) {
          return false;
        }
    
        for (File f : files) {
          if (f.isFile()) {
            if (f.getName().contains(changeId)) {
              return true;
            }
          }
        }
        return false;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    3、每个 changelog 文件中的 changeSet 都有一个 author 属性,用来标注是谁创建的 changelog,目前我的做法是执行终端命令来获取 git 的 userName,如果有更好的实现,望不吝赐教。

    public class GitUtil {
    
      public static String getGitUserName() {
        try {
          String cmd = "git config user.name";
          Process p = Runtime.getRuntime().exec(cmd);
          InputStream is = p.getInputStream();
          BufferedReader reader = new BufferedReader(new InputStreamReader(is));
          String line = reader.readLine();
          p.waitFor();
          is.close();
          reader.close();
          p.destroy();
          return line;
        } catch (IOException | InterruptedException e) {
          e.printStackTrace();
        }
        return "hresh";
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4、生成 xml 格式的 changelog

    @Mojo(name = "generateModelChangeXml", defaultPhase = LifecyclePhase.PACKAGE)
    public class LiquibaseChangeLogXml extends AbstractMojo implements LiquibaseChangeLog {
    
      // 配置的是本maven插件的配置,在pom使用configration标签进行配置 property就是名字,
      // 在配置里面的标签名字。在调用该插件的时候会看到
      @Parameter(property = "sourceFolderPath")
      private String sourceFolderPath;
    
      @Override
      public void execute() throws MojoExecutionException, MojoFailureException {
        System.out.println("Create a new empty model changelog in liquibase yaml file.");
        String userName = GitUtil.getGitUserName();
    
        String changeLogFileName = getChangeLogFileName(sourceFolderPath);
        if (StrUtil.isNotBlank(changeLogFileName)) {
          generateXmlChangeLog(changeLogFileName, userName);
        }
      }
    
      private void generateXmlChangeLog(String changeLogFileName, String userName) {
        String changeLogFileFullName = changeLogFileName + ".xml";
        File file = new File(sourceFolderPath, changeLogFileFullName);
        String content = "\n"
            + "
            + "  xmlns:ext=\"http://www.liquibase.org/xml/ns/dbchangelog-ext\"\n"
            + "  xmlns:pro=\"http://www.liquibase.org/xml/ns/pro\"\n"
            + "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
            + "  xsi:schemaLocation=\"http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd\">\n"
            + "   + userName + "\" id=\"" + changeLogFileName + "\">\n"
            + "  \n"
            + "";
        try {
          FileWriter fw = new FileWriter(file.getAbsoluteFile());
          BufferedWriter bw = new BufferedWriter(fw);
          bw.write(content);
          bw.close();
          fw.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    5、生成 yaml 格式的 changelog

    @Mojo(name = "generateModelChangeYaml", defaultPhase = LifecyclePhase.PACKAGE)
    public class LiquibaseChangeLogYaml extends AbstractMojo implements LiquibaseChangeLog {
    
      // 配置的是本maven插件的配置,在pom使用configration标签进行配置 property就是名字,
      // 在配置里面的标签名字。在调用该插件的时候会看到
      @Parameter(property = "sourceFolderPath")
      private String sourceFolderPath;
    
      @Override
      public void execute() throws MojoExecutionException, MojoFailureException {
        System.out.println("Create a new empty model changelog in liquibase yaml file.");
        String userName = GitUtil.getGitUserName();
    
        String changeLogFileName = getChangeLogFileName(sourceFolderPath);
        if (StrUtil.isNotBlank(changeLogFileName)) {
          generateYamlChangeLog(changeLogFileName, userName);
        }
      }
    
      private void generateYamlChangeLog(String changeLogFileName, String userName) {
        String changeLogFileFullName = changeLogFileName + ".yml";
        File file = new File(sourceFolderPath, changeLogFileFullName);
        String content = "databaseChangeLog:\n"
            + "  - changeSet:\n"
            + "      id: " + changeLogFileName + "\n"
            + "      author: " + userName + "\n"
            + "      changes:";
        try {
          FileWriter fw = new FileWriter(file.getAbsoluteFile());
          BufferedWriter bw = new BufferedWriter(fw);
          bw.write(content);
          bw.close();
          fw.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    6、执行 mvn install 命令,然后会在 maven 的 repository 文件中生成对应的 jar 包。

    项目整体结构如下图所示:

    liquibase 模版生成器项目结构

    因为个人感觉 yaml 文件看起来比较简洁,所以虽然插件提供了两种格式,但后续我选择 yaml 文件。

    Liquibase项目

    本项目只是演示如何通过 Liquibase 新增数据表、修改表字段、初始化数据等功能,并不涉及具体的业务功能,所以代码部分会比较少。

    1、引入依赖

    <parent>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-parentartifactId>
      <version>2.6.3version>
      <relativePath/>
    parent>
    
    <properties>
      <java.version>1.8java.version>
      <mysql.version>8.0.19mysql.version>
      <org.projectlombok.version>1.18.20org.projectlombok.version>
      <druid.version>1.1.18druid.version>
      <liquibase.version>4.16.1liquibase.version>
    properties>
    
    <dependencies>
      
      <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
      dependency>
    
      <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>${mysql.version}version>
        <scope>runtimescope>
      dependency>
      <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>druid-spring-boot-starterartifactId>
        <version>${druid.version}version>
      dependency>
      <dependency>
        <groupId>org.liquibasegroupId>
        <artifactId>liquibase-coreartifactId>
        <version>4.16.1version>
      dependency>
      <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plus-boot-starterartifactId>
        <version>3.5.1version>
      dependency>
      <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plusartifactId>
        <version>3.5.1version>
      dependency>
    
    dependencies>
    
    <build>
      <plugins>
        <plugin>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-maven-pluginartifactId>
        plugin>
        <plugin>
          <groupId>org.liquibasegroupId>
          <artifactId>liquibase-maven-pluginartifactId>
          <version>4.16.1version>
          <configuration>
            
            <propertyFile>src/main/resources/application.ymlpropertyFile>
            <propertyFileWillOverride>truepropertyFileWillOverride>
          configuration>
        plugin>
        <plugin>
          <groupId>com.msdn.hreshgroupId>
          <artifactId>liquibase-changelog-generateartifactId>
          <version>1.0-SNAPSHOTversion>
          <configuration>
            <sourceFolderPath>src/main/resources/liquibase/changelogs/
            sourceFolderPath>
          configuration>
        plugin>
      plugins>
    build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    2、application.yml 配置如下:

    server:
      port: 8088
    
    spring:
      application:
        name: springboot-liquibase
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mysql_db?serverTimezone=Hongkong&characterEncoding=utf-8&useSSL=false
        username: root
        password: root
      liquibase:
        enabled: true
        change-log: classpath:liquibase/master.xml
        # 记录版本日志表
        database-change-log-table: databasechangelog
        # 记录版本改变lock表
        database-change-log-lock-table: databasechangeloglock
    
    mybatis:
      mapper-locations: classpath:mapper/*Mapper.xml
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        lazy-loading-enabled: true
    
    changeLogFile: src/main/resources/liquibase/master.xml
    #输出文件路径配置
    #outputChangeLogFile: src/main/resources/liquibase/out/out.xml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    3、resources 目录下创建 Liquibase 相关文件,主要是 master.xml

    
    <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.8.xsd">
    
      
      <property name="id" value="int(11)" dbms="mysql"/>
      <property name="time" value="timestamp" dbms="mysql"/>
    
      <includeAll path="liquibase/changelogs"/>
    
    databaseChangeLog>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    还需要创建 liquibase/changelogs 目录。

    4、创建一个启动类,准备启动项目

    @SpringBootApplication
    public class LiquibaseApplication {
    
      public static void main(String[] args) {
        SpringApplication.run(LiquibaseApplication.class, args);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接下来我们就进行测试使用 Liquibase 来进行数据库变更控制。

    创建表

    准备通过 Liquibase 来创建数据表,首先点击下面这个命令:

    image-20220927212757676

    然后在控制台输入 create_table_admin,回车,我们可以看到对应的文件如下:

    image-20221124162316987

    我们填充上述文件,将建表字段加进去。

    databaseChangeLog:
      - changeSet:
          id: 20221124_161016_997__create_table_admin
          author: hresh
          changes:
            - createTable:
                tableName: admin
                columns:
                  - column:
                      name: id
                      type: ${id}
                      autoIncrement: true
                      constraints:
                        primaryKey: true
                        nullable: false
                  - column:
                      name: name
                      type: varchar(50)
                  - column:
                      name: password
                      type: varchar(100)
                  - column:
                      name: create_time
                      type: ${time}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    关于 Liquibase yaml SQL 格式推荐去官网查询。

    启动项目后,先来查看控制台输出:

    liquibase执行日志

    接着去数据库中看 databasechangelog 表记录

    databasechangelog 表记录

    以及 admin 表结构

    admin表字段

    新增表字段

    使用我们的模版生成器插件,输入 add_column_address_in_admin,回车得到一个模版文件,比如说我们在 admin 表中新增 address 字段。

    databaseChangeLog:
      - changeSet:
          id: 20221124_163754_923__add_column_address_in_admin
          author: hresh
          changes:
            - addColumn:
                tableName: admin
                columns:
                  - column:
                      name: address
                      type: varchar(100)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    再次重启项目,这里我就不贴控制台输出日志了,直接去数据库中看 admin 表的变化。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VwKKrqFR-1669598492531)(https://www.hreshhao.com/wp-content/uploads/2022/11/image-20221124164430229.png)]

    创建索引

    输入 create_index_in_admin,回车得到模版文件,然后填充内容:

    databaseChangeLog:
      - changeSet:
          id: 20221124_164641_992__create_index_in_admin
          author: hresh
          changes:
            - createIndex:
                tableName: admin
                indexName: idx_name
                columns:
                  - column:
                      name: name
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    查看 admin 表变化:

    admin表字段

    如果要修改索引,一般都是先删再增,删除索引可以这样写:

    databaseChangeLog:
      - changeSet:
          id: 20221124_164641_992__create_index_in_admin
          author: hresh
          changes:
            - dropIndex:
                tableName: admin
                indexName: idx_name
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    初始化数据

    输入 init_data_in_admin ,修改模版文件

    databaseChangeLog:
      - changeSet:
          id: 20221124_165413_348__init_data_in_admin
          author: hresh
          changes:
            - sql:
                dbms: mysql
                sql: "insert into admin(name,password) values('hresh','1234')"
                stripComments:  true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    重启项目后,可以发现数据表中多了一条记录。

    关于 Liquibase 还有很多操作没介绍,等大家实际应用时再去发掘了,这里就不一一介绍了。

    Liquibase 好用是好用,那么有没有可视化的界面呢?答案当然是有的。

    plugin-生成数据库修改文档

    双击liquibase plugin面板中的liquibase:dbDoc选项,会生成数据库修改文档,默认会生成到target目录中,如下图所示

    liquibase文档

    访问index.html会展示如下页面,简直应有尽有

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rNEztBno-1669598492532)(https://www.hreshhao.com/wp-content/uploads/2022/11/image-20221124171209541.png)]

    关于 liquibase 的更多有意思的命令使用,可以花时间再去挖掘一下,这里就不过多介绍了。

    问题

    控制台输出 liquibase.changelog Reading resource 读取了很多没必要的文件

    控制台截图如下所示:

    image-20221124105341305

    我们查找一个 AbstractChangeLogHistoryService 文件所在位置,发现它是 liquibase-core 包下的文件,如下所示:

    liquibase-core文件展示

    为什么会这样呢?首先来看下我们关于 liquibase 的配置,如下图所示:

    image-20221124105629800

    其中 master.xml 文件内容如下:

    
    <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.8.xsd">
    
      <property name="id" value="int(11)" dbms="mysql"/>
      <property name="time" value="timestamp" dbms="mysql"/>
    
      <includeAll path="liquibase/changelog/"/>
    
    databaseChangeLog>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    从上面可以看出,resource 目录下关于 liquibase 的文件夹和 liquibase-core 中的一样,难道是因为重名导致读取了那些文件,我们试着修改一下文件夹名称,将 changelog 改为 changelogs,顺便修改 master.xml。

    再次重启项目,发现控制台就正常输出了。

    简单去看了下 Liquibase 的执行流程,看看读取 changelog 时做了哪些事情,最终定位到 liquibase.integration.spring.SpringResourceAccessor 文件中的 list()方法,源码如下:

    public SortedSet<String> list(String relativeTo, String path, boolean recursive, boolean includeFiles, boolean includeDirectories) throws IOException {
      String searchPath = this.getCompletePath(relativeTo, path);
      if (recursive) {
        searchPath = searchPath + "/**";
      } else {
        searchPath = searchPath + "/*";
      }
    
      searchPath = this.finalizeSearchPath(searchPath);
      Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(this.resourceLoader).getResources(searchPath);
      SortedSet<String> returnSet = new TreeSet();
      Resource[] var9 = resources;
      int var10 = resources.length;
    
      for(int var11 = 0; var11 < var10; ++var11) {
        Resource resource = var9[var11];
        boolean isFile = this.resourceIsFile(resource);
        if (isFile && includeFiles) {
          returnSet.add(this.getResourcePath(resource));
        }
    
        if (!isFile && includeDirectories) {
          returnSet.add(this.getResourcePath(resource));
        }
      }
    
      return returnSet;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    其中 searchPath 变量值为 classpath*:/liquibase/changelog/**,然后通过 ResourcePatternUtils 读取文件时,就把 liquibase-core 包下同路径的文件都扫描出来了。如下图所示:

    image-20221124113407281

    所以我们的应对措施暂时定为修改 changelog 目录名为 changelogs。

    总结

    感兴趣的朋友可以去我的 Github 下载相关代码,如果对你有所帮助,不妨 Star 一下,谢谢大家支持!

  • 相关阅读:
    前端登录时所做的token验证(路由守卫、请求拦截器、响应拦截器)
    联机算法和脱机算法[Alg_001]
    sip服务器 国标gb28181
    夜神安卓7导入charles证书
    20个实用的 TypeScript 单行代码汇总
    决策树可视化-graphviz安装
    放大招,百度文心大模型4.0正在加紧训练,即将发布
    React 官网为什么那么快?
    160_技巧_Power BI 新函数-计算工作日天数
    Android eSIM-LPA基于Android13的实现
  • 原文地址:https://blog.csdn.net/Herishwater/article/details/128073785