Spring Boot整合Liquibase案例

wen java案例 3

Spring Boot 整合 Liquibase 完整案例

项目结构

spring-boot-liquibase-demo/
├── pom.xml
├── src/main/java/com/example/liquibase/
│   ├── LiquibaseApplication.java
│   ├── controller/
│   │   └── UserController.java
│   ├── entity/
│   │   └── User.java
│   ├── mapper/
│   │   └── UserMapper.java
│   └── service/
│       └── UserService.java
├── src/main/resources/
│   ├── application.yml
│   ├── db/
│   │   ├── changelog/
│   │   │   └── db.changelog-master.yaml
│   │   └── changelog/
│   │       ├── 001-create-user-table.yaml
│   │       ├── 002-add-column.yaml
│   │       └── 003-insert-data.yaml
│   ├── mapper/
│   │   └── UserMapper.xml
│   └── schema.sql (可选,用于初始化数据库)
└── src/test/java/com/example/liquibase/
    └── LiquibaseApplicationTests.java

pom.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-boot-liquibase-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-liquibase-demo</name>
    <description>Spring Boot Liquibase 整合案例</description>
    <properties>
        <java.version>17</java.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <mysql.version>8.0.33</mysql.version>
    </properties>
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Boot JPA (可选,如果使用JPA) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- MyBatis Plus (如果使用MyBatis) -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!-- MySQL 驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- Liquibase 核心依赖 -->
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-core</artifactId>
        </dependency>
        <!-- Liquibase 额外支持 (YAML格式) -->
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-maven-plugin</artifactId>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- H2 内存数据库 (用于测试,可选) -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
            <!-- Liquibase Maven Plugin -->
            <plugin>
                <groupId>org.liquibase</groupId>
                <artifactId>liquibase-maven-plugin</artifactId>
                <version>4.27.0</version>
                <configuration>
                    <propertyFile>src/main/resources/liquibase.properties</propertyFile>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

主应用类

package com.example.liquibase;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.liquibase.mapper")
public class LiquibaseApplication {
    public static void main(String[] args) {
        SpringApplication.run(LiquibaseApplication.class, args);
    }
}

application.yml 配置

server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/liquibase_demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: your_password
    driver-class-name: com.mysql.cj.jdbc.Driver
  # Liquibase 配置
  liquibase:
    enabled: true
    change-log: classpath:db/changelog/db.changelog-master.yaml
    # 生成的数据表前缀
    database-change-log-lock-table: DATABASECHANGELOGLOCK
    database-change-log-table: DATABASECHANGELOG
    # 每次启动时强制更新
    drop-first: false
    # 是否启用默认的 schema
    default-schema: public
  # JPA 配置(如果使用)
  jpa:
    hibernate:
      ddl-auto: none  # 让 Liquibase 管理数据库结构
    show-sql: true
    properties:
      hibernate:
        format_sql: true
# MyBatis Plus 配置(如果使用)
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.liquibase.entity
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    org.liquibase: INFO
    com.example.liquibase: DEBUG

Liquibase Changelog 文件

1 主 Changelog 文件 db.changelog-master.yaml

databaseChangeLog:
  # 检查数据库类型,如果是 H2 则使用对应的种类型
  - preConditions:
      - runningAs:
          username: root
  # 包含各个子 changelog 文件
  - include:
      file: classpath:db/changelog/001-create-user-table.yaml
  - include:
      file: classpath:db/changelog/002-add-column.yaml
  - include:
      file: classpath:db/changelog/003-insert-data.yaml
  - include:
      file: classpath:db/changelog/004-add-index.yaml

2 创建用户表 001-create-user-table.yaml

databaseChangeLog:
  - changeSet:
      id: 001-create-user-table
      author: admin
      changes:
        - createTable:
            tableName: user
            remarks: 用户表
            columns:
              - column:
                  name: id
                  type: BIGINT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false
                    primaryKeyName: pk_user_id
                  remarks: 主键ID
              - column:
                  name: username
                  type: VARCHAR(50)
                  constraints:
                    nullable: false
                    unique: true
                  remarks: 用户名
              - column:
                  name: email
                  type: VARCHAR(100)
                  constraints:
                    nullable: false
                    unique: true
                  remarks: 邮箱
              - column:
                  name: password
                  type: VARCHAR(255)
                  constraints:
                    nullable: false
                  remarks: 密码
              - column:
                  name: phone
                  type: VARCHAR(20)
                  remarks: 手机号
              - column:
                  name: status
                  type: TINYINT
                  defaultValueNumeric: 1
                  remarks: 状态(1-正常,0-禁用)
              - column:
                  name: created_at
                  type: TIMESTAMP
                  defaultValueComputed: CURRENT_TIMESTAMP
                  remarks: 创建时间
              - column:
                  name: updated_at
                  type: TIMESTAMP
                  defaultValueComputed: CURRENT_TIMESTAMP
                  remarks: 更新时间

3 添加新列 002-add-column.yaml

databaseChangeLog:
  - changeSet:
      id: 002-add-column
      author: admin
      changes:
        - addColumn:
            tableName: user
            columns:
              - column:
                  name: avatar_url
                  type: VARCHAR(500)
                  remarks: 头像URL
              - column:
                  name: age
                  type: INT
                  remarks: 年龄
        - addNotNullConstraint:
            tableName: user
            columnName: age
            columnDataType: INT
            defaultNullValue: 0
            constraintName: ck_user_age_not_null

4 插入初始数据 003-insert-data.yaml

databaseChangeLog:
  - changeSet:
      id: 003-insert-data
      author: admin
      changes:
        - insert:
            tableName: user
            columns:
              - column: { name: username, value: "admin" }
              - column: { name: email, value: "admin@example.com" }
              - column: { name: password, value: "$2a$10$E0DqKkXrHGzL7lUaBmHnUeGdK5C3wjJYr9O1zNmF8bVkFwYxUyTiW" }
              - column: { name: phone, value: "13800138000" }
              - column: { name: status, valueNumeric: 1 }
              - column: { name: avatar_url, value: "https://example.com/avatar.png" }
              - column: { name: age, valueNumeric: 30 }
        - insert:
            tableName: user
            columns:
              - column: { name: username, value: "test_user" }
              - column: { name: email, value: "test@example.com" }
              - column: { name: password, value: "$2a$10$E0DqKkXrHGzL7lUaBmHnUeGdK5C3wjJYr9O1zNmF8bVkFwYxUyTiW" }
              - column: { name: phone, value: "13900139000" }
              - column: { name: status, valueNumeric: 1 }
              - column: { name: avatar_url, value: null }
              - column: { name: age, valueNumeric: 25 }

5 添加索引 004-add-index.yaml

databaseChangeLog:
  - changeSet:
      id: 004-add-index
      author: admin
      changes:
        - createIndex:
            tableName: user
            indexName: idx_user_email
            unique: true
            columns:
              - column:
                  name: email
        - createIndex:
            tableName: user
            indexName: idx_user_status
            unique: false
            columns:
              - column:
                  name: status

实体类(使用 JPA)

package com.example.liquibase.entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "user")
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false, unique = true)
    private String username;
    @Column(nullable = false, unique = true)
    private String email;
    @Column(nullable = false)
    private String password;
    private String phone;
    private Integer status;
    @Column(name = "avatar_url")
    private String avatarUrl;
    private Integer age;
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
    }
    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }
}

Controller 和 Service

package com.example.liquibase.controller;
import com.example.liquibase.entity.User;
import com.example.liquibase.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.findById(id);
    }
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id);
        return userService.save(user);
    }
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteById(id);
    }
}
package com.example.liquibase.service;
import com.example.liquibase.entity.User;
import com.example.liquibase.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    public List<User> findAll() {
        return userRepository.findAll();
    }
    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    public User save(User user) {
        return userRepository.save(user);
    }
    public void deleteById(Long id) {
        userRepository.deleteById(id);
    }
}

Liquibase 常用配置和属性文件

1 liquibase.properties 文件

# 数据库连接配置
url=jdbc:mysql://localhost:3306/liquibase_demo
username=root
password=your_password
# Liquibase 配置
changeLogFile=classpath:db/changelog/db.changelog-master.yaml
# 日志级别
logLevel=INFO
# 上下文
contexts=dev,prod
# 标签
labels=test

2 不同环境配置

# application-dev.yml
spring:
  liquibase:
    contexts: dev
    change-log: classpath:db/changelog/db.changelog-master.yaml
# application-prod.yml
spring:
  liquibase:
    contexts: prod
    change-log: classpath:db/changelog/db.changelog-prod.yaml

高级功能示例

1 使用 SQL 格式的 changelog

005-custom-sql.yaml:

Spring Boot整合Liquibase案例

databaseChangeLog:
  - changeSet:
      id: 005-custom-sql
      author: admin
      changes:
        - sql:
            sql: |
              ALTER TABLE user 
              ADD COLUMN last_login_date TIMESTAMP;
        - sqlFile:
            path: classpath:db/sql/update-user.sql
            splitStatements: true
            stripComments: true

2 使用存储过程和视图

006-create-view.yaml:

databaseChangeLog:
  - changeSet:
      id: 006-create-view
      author: admin
      changes:
        - createView:
            viewName: v_user_info
            selectQuery: |
              SELECT u.id, u.username, u.email, u.status
              FROM user u
              WHERE u.status = 1
            replaceIfExists: true

测试配置(使用 H2 内存数据库)

# src/test/resources/application-test.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
    username: sa
    password: 
    driver-class-name: org.h2.Driver
  liquibase:
    enabled: true
    change-log: classpath:db/changelog/db.changelog-master.yaml
  jpa:
    hibernate:
      ddl-auto: none
    database-platform: org.hibernate.dialect.H2Dialect

十一、Maven 命令

# 运行 Liquibase 命令
mvn liquibase:status          # 查看变更状态
mvn liquibase:update          # 执行变更
mvn liquibase:rollback        # 回滚变更
mvn liquibase:validate        # 验证 changelog
mvn liquibase:clearCheckSums  # 清除校验和
# 启动应用
mvn spring-boot:run
# 打包并启动
mvn clean package
java -jar target/spring-boot-liquibase-demo-0.0.1-SNAPSHOT.jar

十二、最佳实践建议

  1. 使用规范化命名:changelog 文件按日期或序号命名,如 20240101-001-create-table.yaml

  2. 只增不改:已经执行过的 changeSet 不要修改内容,如果要修改请创建新的 changeSet

  3. 使用上下文和环境:通过 contextslabels 控制不同环境执行的 changelog

  4. 添加验证条件:使用 preConditionsvalidate 保证数据一致性

  5. 使用变量和模板:可以通过 语法使用环境变量

  6. 版本控制:将 changelog 文件纳入版本控制,保持变更历史清晰

  7. 测试:在测试环境先行验证 changelog 脚本的正确性

  8. 文档:每个 changeSet 添加清晰的 remarks 说明

databaseChangeLog:
  - changeSet:
      id: 20240101-001-create-user-table
      author: admin
      remarks: 创建用户表
      changes:
        - createTable: ...

这个案例展示了 Spring Boot 与 Liquibase 的完整整合方案,包括数据库版本管理、自动更新、回滚等核心功能,记得根据实际项目需求调整配置和目录结构。

抱歉,评论功能暂时关闭!