在多租户场景下,利用liquibase实现数据库版本管理

摘要 :在SpringBoot的默认单一数据源下,liquibase能够进行数据库版本管理,如果是多租户情况下,则需要手动实现,本文主要介绍如何利用liquibase来实现多租户场景下的数据库版本管理。

简介

本文介绍的多租户场景,是多个用户数据库,使用的是同一个服务实例,然后通过header传递的tenant-id来区分具体使用哪一个数据库实例(如何实现多租户就不展开了,其他文章细说)。而数据库的版本管理,则需要统一进行管理,利用了SpringLiquibase类提供的管理能力,Hikari的数据库连接池。

配置说明

pom.xml文件

xml 复制代码
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-core</artifactId>
        </dependency>
    </dependencies>

resource目录下的配置

mysql.xml

该文件配置数据库版本更新语句的目录

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.1.xsd">

    <!--  初始化表结构  -->
    <include file="/liquibase/mysql/*.sql"/>
</databaseChangeLog>

init.sql

sql 复制代码
--liquibase formatted sql

--changeset nise:202311201420
CREATE TABLE `USER` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='用户';

LiquibaseDataSourceProperties 属性配置类

arduino 复制代码
@Data
public class LiquibaseDataSourceProperties {

    /** 驱动名称 */
    private String driverClassName;
    /** 链接地址 */
    private String jdbcUrl;
    /** 用户名 */
    private String username;
    /** 密码 */
    private String password;
    /** 数据库schema */
    private String schema;

}

schema说明

也可以理解为数据库的隔离一种方式,只不过每一种数据库的表现形式不一样

  • mysqlschema和数据库的名称一样,同一个名称下只有一个库。
  • oracleschema就是数据库名称,oracle一般采用服务名连接的。
  • kingbaseschema是数据库里面的具体空间名称,比如public,如果使用该方式实现多租户,则可以在同一个库里面通过命名空间区分。

LiquibaseService

管理服务,这里简单写一下,最好写到try cache finally中来关闭数据库连接,防止失败。

scss 复制代码
@Service
public class LiquibaseService {

    @Autowired
    private ResourceLoader resourceLoader;

    /**
     * 初始化数据库
     * @param properties
     */
    public void init(LiquibaseDataSourceProperties properties) throws Exception{
        HikariDataSource hikariDataSource = this.buildDataSource(properties);
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setDataSource(hikariDataSource);
        liquibase.setResourceLoader(resourceLoader);
        //指定changelog的位置,这里使用的一个master文件引用其他文件的方式
        liquibase.setChangeLog("classpath*:liquibase/mysql.xml");
        liquibase.setShouldRun(true);
        liquibase.afterPropertiesSet();
        hikariDataSource.close();
    }

    private HikariDataSource buildDataSource(LiquibaseDataSourceProperties properties){
        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setDriverClassName(properties.getDriverClassName());
        hikariDataSource.setJdbcUrl(properties.getJdbcUrl());
        hikariDataSource.setUsername(properties.getUsername());
        hikariDataSource.setPassword(properties.getPassword());
        hikariDataSource.setSchema(properties.getSchema());
        hikariDataSource.setMinimumIdle(1);
        hikariDataSource.setMaximumPoolSize(1);
        return hikariDataSource;
    }

}

服务调用

一般采用的是多数据源方式,或者采用从缓存中读取多租户的配置信息,然后初始化数据库;下面我就手动调用来模拟实现。

less 复制代码
@RestController
@RequestMapping(value = "liquibase")
public class LiquibaseController {

    @Autowired
    private LiquibaseService liquibaseService;

    @PostMapping(value = "init")
    public String init(@RequestBody LiquibaseDataSourceProperties properties) throws Exception{
        liquibaseService.init(properties);
        return "SUCCESS";
    }

}

结果

最终会生成两张数据库管理表DATABASECHANGELOGDATABASECHANGELOGLOCK和自己的业务表

相关推荐
再吃一根胡萝卜5 小时前
微服务治理的“四大护法”:从 Django 视角理解 Spring Cloud 核心组件
后端
再吃一根胡萝卜5 小时前
面试终极挑战:如何用 Django 经验,回答 Java 微服务实战问题?
后端
To_OC6 小时前
装完 ESLint 它一声不吭?我还以为代码写得多好
后端·node.js·eslint
Justin3go6 小时前
独立工具的流量到底从哪来:Direct、Referral 与 AI 引荐的崛起
数据分析·saas
Justin3go7 小时前
免费、免费增值、付费:5900 款独立工具告诉我们的定价与增长真相
数据分析·saas
Justin3go7 小时前
高增长独立产品分析:333 个快速增长产品的共性
数据分析·saas
凤山老林7 小时前
精细化流量治理:Spring Boot 动态特性开关与灰度发布体系
java·spring boot·后端
vipbic8 小时前
一个前端的 9 天重构:我是怎么用 Codex 重做航栈的
前端·javascript·后端
Aphelios3809 小时前
一次锁内网络IO引发的Tomcat线程池“饿死”事故
java·开发语言·spring boot·elasticsearch·tomcat·网络io阻塞·线程池耗尽
考虑考虑10 小时前
Excel导入时产生特殊字符处理
java·后端·java ee