写在数据背后的脚本:Flyway 与结构演化的故事

目录

1、什么是 Flyway?

2、引入依赖

3、在yml中配置

3.1、在代码中自定义配置

 3.1.1、读取配置

 3.1.2 、自定义读取的flyway 的 读取路径地址

4、sql脚本位置

5、脚本命名规范(非常重要)

6、使用注意事项

📌 脚本注意事项:

7、数据库记录表(flyway_schema_history)

8、Flyway 配置注意事项:


🐦 1、什么是 Flyway?

Flyway 是一个开源的数据库版本控制工具,主要用于:

  • 管理数据库结构的版本迁移(增删表、字段、索引等)

  • 保证数据库 schema 一致性

  • 和 Git 管理代码一样管理数据库迁移脚本

它支持多种数据库(如 MySQL、PostgreSQL、Oracle、SQL Server 等),并能与 Spring Boot、Maven、Gradle 等无缝集成。

2、引入依赖

plugins {
    id 'org.flywaydb.flyway' version '7.15.0'  // 与 flyway-core 7.15.0 兼容
}
   
implementation 'org.flywaydb:flyway-core:7.15.0'

3、在yml中配置

简单自动执行规定目录下的脚本

spring.flyway.enabled=true
spring.flyway.locations=classpath:scripts/db/flyway/mysql
spring.flyway.clean-disabled=true
spring.flyway.baseline-on-migrate=true
spring.flyway.out-of-order=true
spring.flyway.schemas=etrade
spring.flyway.default-schema=etrade

3.1、在代码中自定义配置

可能因为不同的客户需要执行不同的sql 脚本文件

 3.1.1、读取配置

@Configuration
@Data
public class FlywayCustomConfig {

    @Value("${spring.flyway.create-schemas:true}")
    private Boolean createSchemas;

    @Value("${spring.flyway.clean-disabled:true}")
    private Boolean cleanDisabled;

    @Value("${spring.flyway.baseline-on-migrate:true}")
    private Boolean baselineOnMigrate;

    @Value("${spring.flyway.out-of-order:true}")
    private Boolean outOfOrder;

    @Value("${spring.flyway.default-schema:etrade}")
    private String defaultSchema;

    @Value("${spring.flyway.locations:classpath:scripts/db/flyway/mysql}")
    private String[] locations;

}

 3.1.2 、自定义读取的flyway 的 读取路径地址



/**
 * 初始化Flyway ---> 执行相关SQL脚本
 */
@Slf4j
@Configuration
@RequiredArgsConstructor
// flyway开关,可在application.yml中配置
//@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", havingValue = "true")
public class FlywayMigrationPostProcessor implements BeanPostProcessor, Ordered {

    private static final String VERSION = "version";

    private static final String FLYWAY_PATH_PREFIX = "classpath:";

    private final DataSource dataSource;

    private final FlywayCustomConfig flywayCustomConfiguration;

    private final RedissonClient redissonClient;

    private final SystemVariableRepo systemVariableRepo;

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }

    @PostConstruct
    public void init() {
        long start = System.currentTimeMillis();
        log.info("Flyway 执行的脚本目录: {}", String.join(",", flywayCustomConfiguration.getLocations()));
        String redisKey = "FLYWAY_LOCK";
        RLock lock = redissonClient.getLock(redisKey);
        try {
            boolean locked = lock.tryLock(60, 100, TimeUnit.SECONDS);
            log.info("Flyway 配置: {}", flywayCustomConfiguration);
            if (!locked) {
                throw new CustomException("请稍后再试");
            }
            // 执行 Flyway 初始化脚本
            executeFlyway(start);
        } catch (Exception e) {
            log.error("Flyway 初始化失败,原因:{}", e.getMessage(),e);
            throw new CustomException("Flyway 初始化过程中发生错误" + e);
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }


    private void executeFlyway(Long start) {
        log.info("开始执行 Flyway 迁移...");
        try {
            SqlUtils.setDbType(SqlUtils.getDbType().name());
    
            String schema = resolveSchema(flywayCustomConfiguration.getDefaultSchema());

            String[] locationsArray = flywayCustomConfiguration.getLocations();
            List<String> locations = Lists.newArrayList(locationsArray);

            List<SystemVariable> systemVariables = systemVariableRepo.findAll();
            String path = locationsArray[0];
            // 文件名列表
            List<String> fileNameList = ListUtil.of(new PathMatchingResourcePatternResolver().getResources(path + "/*")).stream()
                    .filter(resource -> resource.exists() && resource.getFilename() != null)
                    .map(Resource::getFilename)
                    .sorted()
                    .collect(Collectors.toList());
            if (CollUtil.isNotEmpty(fileNameList)) {
                locations.remove(0);
                fileNameList.stream()
                        .filter(name -> name != null &&
                                (name.toLowerCase().contains(VERSION) ||
                                        systemVariables.stream().anyMatch(var -> name.equals(var.getName()))))
                        .map(name -> path + "/" + name)
                        .forEach(locations::add);
            }
            // 创建 Flyway 实例并配置参数
            Flyway.configure()
                    .dataSource(dataSource) // 数据源
                    .locations(locations.toArray(new String[0])) // 迁移脚本的路径
                    .createSchemas(flywayCustomConfiguration.getCreateSchemas()) // 是否创建架构
                    .cleanDisabled(flywayCustomConfiguration.getCleanDisabled()) // 是否禁用清理
                    .baselineOnMigrate(flywayCustomConfiguration.getBaselineOnMigrate()) // 在迁移时是否使用基线
                    .outOfOrder(flywayCustomConfiguration.getOutOfOrder()) // 是否允许脚本超出顺序
                    .schemas(schema) // 要求和defaultSchema一致
                    .defaultSchema(schema) // 默认 schema
                    .load().migrate();
            ProfilingUtils.logTime(start, "Flyway 迁移完成【SQL脚本执行完成......】", null);
        } catch (Exception e) {
            log.error("Flyway 初始化失败,原因:{}", e.getMessage(), e);
            throw new CustomException("Flyway 初始化过程中发生错误");
        }
    }

    public String resolveSchema(String schema) {
        DbTypeEnum dbType = SqlUtils.getDbType();
        if (schema == null) return null;
        return (dbType == DbTypeEnum.ORACLE || dbType == DbTypeEnum.DAMENG)
                ? schema.toUpperCase()
                : schema;
    }
}

4、sql脚本位置

📝 5、脚本命名规范(非常重要)

Flyway 通过文件名来识别版本和顺序,命名规范如下:

✅ 命名格式:

V版本号__描述.sql

✅ 示例:

V1__init_schema.sql
V2__add_user_table.sql
V3_1__add_index.sql     (小版本) 

  • V1V2 是版本号(不可重复

  • __ 是两个下划线(不能少于两个

  • .sql 为 SQL 脚本

  • Java 脚本则用 *.java 但使用较少

⚠️ 6、使用注意事项

📌 脚本注意事项:

  1. 不可修改已执行的脚本
    因为 Flyway 会记录执行历史,一旦执行不能改内容,否则校验失败。

  2. 每个脚本只能表示一个逻辑版本
    避免一个脚本里做太多事情,不好排查错误。

  3. 不要跳过版本号
    不建议直接跳 V1 → V5,这可能引起迁移混乱。

  4. 脚本必须是幂等的(尽可能)
    避免多次执行报错,适当使用 IF NOT EXISTS 等。

  5. 字符集要统一(一般为 UTF-8)

7、数据库记录表(flyway_schema_history)

Flyway 会自动生成一个表来记录迁移历史,表名默认是:

 flyway_schema_history

 

📌 8、Flyway 配置注意事项:

配置项说明
baseline-on-migrate对已有数据库初始化时设为 true(防止已有表报错)
clean-disabled防止误删库结构,生产环境建议设为 true
out-of-order是否允许乱序执行版本号,建议设为 false
locations指定脚本目录,支持多目录
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大大怪~将军

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值