Flyway数据库版本管理
一、为什么要使用flyway
1、描述
- 迁移指的是把数据库结构从一个版本更新到另一个版本。使用
Flyway
时,对数据库的所有更改都称为迁移。- 迁移可以是版本化,也可以是可重复。迁移脚本分为三种类型:
Versioned
、Repeatable和Undo
。- 其中,
Versioned
用于版本升级,每个版本有唯一的版本号并只能执行一次;Repeatable
可重复执行,当Flyway
检测到Repeatable
类型的SQL
脚本的checksum
有变动,Flyway
就会重新应用该脚本;Undo
用于撤销上一次的迁移操作。
2、为什么要使用flyway
- 在多人开发的项目中,我们都习惯了使用
SVN
或者Git
来对代码做版本控制,主要的目的就是为了解决多人开发代码冲突和版本回退的问题。- 数据库的变更也需要版本控制,在日常开发中,我们经常会遇到下面的问题:
- 自己写的SQL忘了在所有环境执行。
- 别人写的SQL我们不能确定是否都在所有环境执行过了。
- 有人修改了已经执行过的SQL,期望再次执行。
- 需要新增环境做数据迁移。
- 每次发版需要手动控制先发DB版本,再发布应用版本。
- 其它场景。
3.flyway是如何工作的
- 项目启动,应用程序完成数据库连接池的建立后,
Flyway
自动运行。- 初次使用时,
flyway
会创建一个flyway_schema_history
表,用于记录sql
执行记录。Flyway
会扫描项目指定路径下(默认是classpath:db/migration
)的所有sql
脚本,与flyway_schema_history
表脚本记录进行比对。如果数据库记录执行过的脚本记录,与项目中的sql
脚本不一致,Flyway
会报错并停止项目执行。- 如果校验通过,则根据表中的sql记录最大版本号,忽略所有版本号不大于该版本的脚本。再按照版本号从小到大,逐个执行其余脚本。
二、SpringBoot3引用依赖
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>10.17.2</version>
</dependency>
<!--必须同时引入flyway-mysql,否则会报不支持mysql8.1版本数据库。-->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
<version>10.17.2</version>
</dependency>
<!--集成MyBatis-Plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.3</version>
</dependency>
集成flyway,sql脚本不执行问题
需整合mybatis,否则启动无法执行脚本
集成flyway,MySQL中root无法访问数据库问题,必须确保外部对root所有数据库的访问权限
GRANT ALL PRIVILEGES ON . TO 'root'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;
三、命名规则
1、仅需要被执行一次的SQL命名以大写的"V"开头,V+版本号(版本号的数字间以”.“或”_“分隔开)+双下划线(用来分隔版本号和描述)+文件描述+后缀名。例如:V20201100__create_user.sql、V2.1.5__create_user_ddl.sql、V4.1_2__add_user_dml.sql。
2、可重复运行的SQL,则以大写的“R”开头,后面再以两个下划线分割,其后跟文件名称,最后以.sql结尾。(不推荐使用)比如:R__truncate_user_dml.sql。
四、默认存放文件路径db.migration(因为flyway也遵循约定大于配置原则)
五、springboot的yml配置
spring:
# 数据库版本管理,SQL执行
flyway:
# 是否启用flyway
enabled: true
# 编码格式,默认UTF-8
encoding: UTF-8
# 迁移sql脚本文件存放路径,官方默认db/migration
locations: classpath:db/migration
# 迁移sql脚本文件名称的前缀,默认V
sql-migration-prefix: V
# 迁移sql脚本文件名称的分隔符,默认2个下划线__
sql-migration-separator: __
# 避免带${}sql执行失败
placeholder-prefix: '#('
placeholder-suffix: )
# 迁移sql脚本文件名称的后缀
sql-migration-suffixes: .sql
# 迁移时是否进行校验,默认true
validate-on-migrate: true
# 历史表,当迁移发现数据库非空且存在没有元数据的表时,自动执行基准迁移,新建schema_version表
baseline-on-migrate: true
# 默认1,从0开始可以规避版本1不执行问题
baseline-version: 0
# 避免数据被意外清空
clean-disabled: false
datasource:
url: jdbc:mysql://localhost:3306/top
username: root
password: hlb159765
driver-class-name: com.mysql.cj.jdbc.Driver
六、版本化迁移(V)
常见的迁移类型是版本化迁移。版本必须是唯一的。 版本化迁移仅按顺序应用一次。 版本化迁移通常用于:
- 正在创建/更改/删除表/索引/外键…
- 数据更新
七、可重复(R)
一般为可重复执行的sql语句。
八、撤销(U)
撤消迁移内容与常规版本化迁移相反。一般不推荐使用,因为版本一但需要回退,可能有破坏性的操作已经执行,我们需要经过充分测试的备份和有一些还原数据的策略。
九、错误收集和解决方案
1.如果 flyway 不是项目初期引入,而是在已有表的情况下引入,必须设置 baseline-on-migrate: true。启动项目后,flyway 就会自动在数据库中创建 flyway_schema_history 表,并且会往该表中插入一条 version = 1 的建表记录,如果迁移数据有 V1__ 开头的文件,扫描文件会忽略该文件不执行迁移,进而可能引发其他迁移数据出错的问题。这个原因是因为baseline-version的默认值就是1。
2.生产环境需要将clean-disabled设置为false,避免数据被意外清空。
踩坑日记
- 禁用 Flyway(如果不需要数据库迁移)报错
spring:
flyway:
enabled: false
禁用flyway之后报错
Description:
Field flyway in com.example.springbootopenaicommon.config.FlywayRepair required a bean of type 'org.flywaydb.core.Flyway' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'org.flywaydb.core.Flyway' in your configuration.
解决方法
- 修改FlywayRepair类 将@Component注解替换为@Configuration并添加@ConditionalOnProperty 注解。
import org.flywaydb.core.Flyway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", havingValue = "true", matchIfMissing = true)
public class FlywayRepair {
@Autowired
private Flyway flyway;
public void repair() {
flyway.repair();
}
}
- 在 application.yml 中控制 Flyway 启用状态
spring:
flyway:
enabled: false # 禁用 Flyway
其他方法
- 方法一:使用 @ConditionalOnBean 注解 可以使用 @ConditionalOnBean 注解,使得 FlywayRepair 类仅在 Flyway Bean 存在时才被创建。
import org.flywaydb.core.Flyway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnBean(Flyway.class)
public class FlywayRepair {
@Autowired
private Flyway flyway;
public void repair() {
flyway.repair();
}
}
- 方法二:显式检查 Flyway 的存在 在代码中显式检查 Flyway 是否存在,并根据检查结果执行不同的逻辑。
import org.flywaydb.core.Flyway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class FlywayRepair {
@Autowired
private ApplicationContext applicationContext;
public void repair() {
if (applicationContext.containsBean("flyway")) {
Flyway flyway = (Flyway) applicationContext.getBean("flyway");
flyway.repair();
}
}
}