1、基于springboot+jpa 实现多租户动态切换多数据源 - 数据隔离方案选择分库还是分表
2、基于springboot+jpa 实现多租户动态切换多数据源 - 基于dynamic-datasource实现多租户动态切换数据源
3、基于springboot+jpa 实现多租户动态切换多数据源 - 使用Flyway实现多数据源数据库脚本管理和迭代更新
自从项目变成了多数据源架构,不同数据源的数据库表结构还不同,JPA的自动生成表结构功能已经不能再使用了,需要改为原生sql来支持多数据源。
每次服务的代码更新部署,难免会存在数据库结构的变更以及字典数据的添加,手动执行更新脚本是一个耗时耗力的工作,而且还会出现遗漏或者其他状况,这时急需一个自动执行数据库脚本的工具来解决问题
为此我做了相关调研,发现常见的开源迁移工具 Liquibase 和 Flyway使用和对比最多,那么就进行一场二选一的角逐吧!
Flyway 和 Liquibase都支持专业数据库重构和版本控制所需的所有功能,因此您将始终知道要处理的数据库模式的版本以及它是否与软件版本匹配。两种工具都集成在 Maven或 Gradle 构建脚本中以及 Spring Boot 生态系统中,因此您可以完全自动化数据库重构。
对比项 | Flyway | Liquibase |
---|---|---|
官网 | Welcome To Flyway | Liquibase简介 |
描述 | 创造者是一家名为Redgate的公司,它被描述为一个开源的数据库迁移工具,它更倾向于简单性和惯例而不是配置。 | 开始于2006年,是一个用于数据库迁移的开源工具。 |
支持的数据库 | 截至目前(2022-12-29),支持大多数数据库,有 Oracle、 SQL Server(包括 Amazon RDS 和 Azure SQL 数据库)、 Azure Synapse(以前的数据仓库)、 DB2、 MySQL(包括 Amazon RDS、Azure 数据库和 Google Cloud SQL)、 Aurora MySQL、 MariaDB、 Percona XtraDB Cluster、 测试容器、 PostgreSQL(包括 Amazon RDS、Azure 数据库、Google Cloud SQL、TimescaleDB、YugabyteDB 和 Heroku)、 Aurora PostgreSQL、 Redshift、 CockroachDB、 SAP HANA、 Sybase ASE、 Informix、 H2、 HSQLDB、 Derby、 Snowflake、 SQLite和 Firebird。 | 支持大多数数据库,有Postgres、Oracle、DB2、H2、MariaDB、SQL Server、SQLite,以及其他许多数据库。许多基于云的数据库也被支持,例如Azure SQL、Amazon RDS、Amazon Aurora。 |
编写方式 | 脚本可以用纯SQL编写(支持许多方言)或用Java编写(主要用于更复杂的转换) | 它是基于变化日志和变化集文件的概念,这些文件可以用SQL、XML、YAML、JSON来写。 |
使用方式 | 有一个命令行客户端,但也提供Maven和Gradle插件。更重要的是,它有Java API,也适用于Android。 | 可以从shell中运行Liquibase迁移脚本,使用Maven Gradle甚至Ant等构建工具。此外,可以生成纯SQL查询去执行。 |
实现数据库变更原理:
实现数据库变更原理:
两者的基本功能其实都差不多
较大区别是 Flyway 的变更以纯 SQL 为脚本,简单直接;Liquibase 比较厚重,当然花样也比较多,包括:
综上所述,我在项目中选择了 Flyway。原因有二:
<dependency>
<groupId>org.flywaydbgroupId>
<artifactId>flyway-coreartifactId>
<version>7.10.0version>
dependency>
#mysql environment
spring:
datasource:
dynamic:
hikari:
connection-timeout: 5000
idle-timeout: 30000 # 经过idle-timeout时间如果连接还处于空闲状态, 该连接会被回收
min-idle: 5 # 池中维护的最小空闲连接数, 默认为 10 个
max-pool-size: 16 # 池中最大连接数, 包括闲置和使用中的连接, 默认为 10 个
max-lifetime: 60000 # 如果一个连接超过了时长,且没有被使用, 连接会被回收
is-auto-commit: true
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: true #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master:
url:
username:
password:
driver-class-name: com.mysql.cj.jdbc.Driver
init:
schema: db/primary_db_table.sql
# 上面是多数据源配置,下面开始才是flyway配置
flyway:
# 是否启用flyway
enabled: true
# metadata 版本控制信息表 默认 flyway_schema_history
table: flyway_schema_history_test
# 如果没有元数据表,在执行flyway migrate命令之前, 必须先执行flyway baseline命令
# 非空数据库初始化Flyway时需要打开此开关进行Baseline操作
baseline-on-migrate: true
# 执行时标记的tag 默认为<>
baseline-description: <>>
# 是否可以无序执行 开发环境建议 true 生产环境建议 false
out-of-order: false
# 执行迁移时是否自动调用验证SQL文件是否存在问题,当你的版本不符合逻辑时会抛出异常
validate-on-migrate: true
# SQL 脚本的目录 默认值 classpath:db/migration
# 这里写项目启动时的主库sql变更版本路径,然后在配置类中根据不同的数据源转换即可
locations: classpath:db/primary
根据在yaml配置文件的脚本存放路径的配置
在resource目录下建立文件夹db/primary、db/migration
可以看到我这里classpath:db/路径下有primary 和 migration两个文件夹,这里就为了不同的数据库表结构可以执行不同的sql脚本,而不会相互影响到
sql脚本的命名规范为:V+版本号(版本号的数字间以”.“或”_“分隔开)+双下划线(用来分隔版本号和描述)+文件描述+后缀名,例如:V1.0.1__update_org_config_db_table.sql。
具体可参考上下文
常用的迁移方式就是基于sql的迁移,如上图,我就是采用的sql迁移方式,但是文件命名有一定的规则
文件名由如下几部分组成:
上面的这些组成部分都是可以进行配置的,更多的配置可以参考官方文档:Flyway Documentation > Flyway CLI and API > Configuration
Configuration旧文档,感觉更好理解点
结合yaml配置和项目文件结构图理解,其实本质就是将application文件的flyway的locations设置为db/primary主库的路径,让主库只去执行primary文件夹下的sql文件,然后在配置类中拆解locations,根据不同的数据源重新组成对应的locations路径。
配置类的代码如下:
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flywaydb.core.Flyway;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.Map;
@Slf4j
@Configuration
@RequiredArgsConstructor
@EnableTransactionManagement
public class FlywayConfig {
private final DataSource dataSource;
@Value("${spring.flyway.locations}")
private String SQL_LOCATION;
@Value("${spring.flyway.table}")
private String VERSION_TABLE;
@Value("${spring.flyway.baseline-on-migrate}")
private boolean BASELINE_ON_MIGRATE;
@Value("${spring.flyway.out-of-order}")
private boolean OUT_OF_ORDER;
@Value("${spring.flyway.validate-on-migrate}")
private boolean VALIDATE_ON_MIGRATE;
@Bean
@PostConstruct
public void migrateOrder() {
log.info("调用数据库生成工具");
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
Map<String, DataSource> dataSources = ds.getDataSources();
dataSources.forEach((k, v) -> {
log.info("正在执行多数据源生成数据库文件: " + k);
if (k.equals("master")) {
log.info("正在执行后台库数据源生成数据库文件");
// 将路径转换
SQL_LOCATION = SQL_LOCATION.split("/")[0] + "/primary";
Flyway flyway = Flyway.configure()
.dataSource(v)
.locations(SQL_LOCATION)
.baselineOnMigrate(BASELINE_ON_MIGRATE)
.table(VERSION_TABLE)
.outOfOrder(OUT_OF_ORDER)
.validateOnMigrate(VALIDATE_ON_MIGRATE)
.load();
flyway.migrate();
} else {
log.info("正在执行多机构数据源生成数据库文件");
// 将路径转换
SQL_LOCATION = SQL_LOCATION.split("/")[0] + "/migration";
Flyway flyway = Flyway.configure()
.dataSource(v)
.locations(SQL_LOCATION)
.baselineOnMigrate(BASELINE_ON_MIGRATE)
.table(VERSION_TABLE)
.outOfOrder(OUT_OF_ORDER)
.validateOnMigrate(VALIDATE_ON_MIGRATE)
.load();
flyway.migrate();
}
});
}
}
启动日志验证
在数据库中可以看到已按照定义好的脚本,完成数据库变更,并在flyway_schema_history表插入了sql执行记录:
如果上述验证都没问题了,那就是没有问题了
下面的注意事项一定要认真仔细的阅读,不然可能就删库跑路了
V+版本号+双下划线+脚本变更描述+后缀
例如:V1.1.0__create_table.sql
flyway.sql-migration-prefix配置前缀,默认V
flyway.sql-migration-separator配置分隔符,默认双下划线
flyway.sql-migration-suffix配置脚本后缀,默认.sql特别注意:V1__.sql == V1.0__.sql V1.1__.sql == V1.1.0__.sql
所以配置时,最好设置显示递增的版本号,否则会报错
文件的版本号必须 > 基线初始版本号,否则不会执行你的脚本
比如基线默认版本号为1,所以你的脚本版本号必须大于1,例如V1.1****
例如文件为:V1.0.0__create_table.sql,你会发现该脚本并不会执行
如果项目已经执行了过了某个脚本,那么这个脚本不能删除,也不能修改,否则在项目启动时会报错
两个脚本的版本号应该严格不同,不能出现1中的特别注意项
特别注意:V1__.sql == V1.0__.sql V1.1__.sql == V1.1.0__.sql
所以配置时,最好设置显示递增的版本号,否则会报错
flyway.clean-on-validation-error:这个配置项一定要小心了
如果配置为true,当你的sql脚本执行失败时,就会执行删除库中所有表的操作,即之前的clean操作,所以一定要慎重,慎重,慎重!!!
Flyway通过 SQL 脚本来执行数据库的建立与更新。当同时集成了 Druid 和 Flyway 之后,Druid 的 wall防火墙极可能直接干预 SQL 脚本的操作,继而导致 Flyway 执行中断。在项目开发的过程中,配置以下防火墙属性以放行 Flyway 的SQL 操作
spring:
datasource:
druid:
wall:
config:
variantCheck: false
noneBaseStatementAllow: true
commentAllow: true
multiStatementAllow: true
执行失败的 SQL 文件一定要成功解决,然后确保application 启动成功后,再提交代码到远程。
由于Flyway导致application执行失败时,元数据表会有错误记录
在console 中查看错误日志及原因,如下图:
有问题的脚本:V1.0.3__creat_table.sql
具体错误信息:Table ‘df_org_config_test_001’ already exists
我的具体错误信息是已经存在了表df_org_config_test_001,那么我修改为创建表df_org_config_test_002
在数据库中打开 df_flyway_schema_history_test 表,删除 success 字段为 0 (0状态为失败,1状态为成功) 的记录(表中最后一条记录)
一般来说这样就解决了问题,如果还是启动不成功,那就要再仔细检查一下脚本了
多数据源系列目前就告一段落了!!!!
Flyway针对多数据源配置实战方案
SQL脚本难以维护,各环境数据库版本不一致该怎么办?
Spring Boot - Flyway Database