在 Spring Boot 中使用 Flyway 进行数据库迁移
在之前的 Spring Boot JdbcTemplate 教程 中,我们见识了如何使用 schema.sql
和 data.sql
脚本初始化数据库。这对于演示项目可能有用,但对于实际应用,我们应该使用数据库迁移工具。
Flyway 是最流行的基于 Java 的数据库迁移工具。可以将 Flyway 作为独立库,或使用 flyway-maven-plugin
或使用 Flyway Gradle
插件进行数据库迁移。
Spring Boot 提供了开箱即用的支持,用于 Flyway 数据库迁移。让我们看看如何创建一个使用 Spring Data JPA 与 PostgreSQL 数据库交互,并使用 Flyway 实现数据库迁移的 Spring Boot 应用。
首先,访问 https://start.springboot.io/,选择 Spring Web
、Spring Data JPA
、PostgreSQL Driver
、Flyway Migration
和 Testcontainers
starter,创建 Spring Boot 应用程序。
创建 Flyway 迁移脚本
Flyway 遵循 V<VERSION>__<DESCRIPTION>.sql
命名约定来命名其版本化的迁移脚本。让我们在 src/main/resources/db/migration
文件夹下添加以下两个迁移脚本。
V1__create_tables.sql:
create table bookmarks
(
id bigserial not null,
title varchar not null,
url varchar not null,
created_at timestamp,
primary key (id)
);
V2__create_bookmarks_indexes.sql:
CREATE INDEX idx_bookmarks_title ON bookmarks(title);
使用 Testcontainers 建立 Postgres 数据库
Spring Boot 3.1.0 引入了对 Testcontainers 的支持,我们可以用它来编写集成测试和本地开发。
在生成应用时,我们选择了 PostgreSQL Driver
和 Testcontainers
starter。因此,生成的应用程序将在 src/test/java
包下有一个 TestApplication.java
,内容如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;
@TestConfiguration(proxyBeanMethods = false)
public class TestApplication {
@Bean
@ServiceConnection
PostgreSQLContainer<?> postgresContainer() {
return new PostgreSQLContainer<>(DockerImageName.parse("postgres:15-alpine"));
}
public static void main(String[] args) {
SpringApplication
.from(Application::main)
.with(TestApplication.class)
.run(args);
}
}
执行 Flyway 迁移
现在,你可以在 IDE 中运行 TestApplication
类,或在命令行中运行 ./mvnw spring-boot:test-run
来启动应用。然后,你会发现以下与 Flyway 执行相关的日志:
INFO 4009 --- [ main] tc.postgres:15-alpine : Container is started (JDBC URL: jdbc:postgresql://127.0.0.1:33331/test?loggerLevel=OFF)
INFO 4009 --- [ main] o.f.c.internal.license.VersionPrinter : Flyway Community Edition 9.16.3 by Redgate
INFO 4009 --- [ main] o.f.c.internal.license.VersionPrinter : See release notes here: https://rd.gt/416ObMi
INFO 4009 --- [ main] o.f.c.internal.license.VersionPrinter :
INFO 4009 --- [ main] o.f.c.i.database.base.BaseDatabaseType : Database: jdbc:postgresql://127.0.0.1:33331/test (PostgreSQL 15.3)
INFO 4009 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Schema history table "public"."flyway_schema_history" does not exist yet
INFO 4009 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 2 migrations (execution time 00:00.010s)
INFO 4009 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table "public"."flyway_schema_history" ...
INFO 4009 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "public": << Empty Schema >>
INFO 4009 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "public" to version "1 - create tables"
INFO 4009 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "public" to version "2 - create bookmarks indexes"
INFO 4009 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 2 migrations to schema "public", now at version v2 (execution time 00:00.041s)
Flyway 默认会在 flyway_schema_history
表中记录所有应用迁移的历史。如果现在查看 flyway_schema_history
表中的数据,可以看到以下几行:
| installed_rank | version | description | type | script | checksum | installed_by | installed_on | execution_time | success |
|:---------------|:--------|:-------------------------|:-----|:---------------------------------|:-----------|:-------------|:---------------------------|:---------------|:--------|
| 1 | 1 | create tables | SQL | V1__create_tables.sql | 1020037327 | test | 2023-08-09 09:13:04.439012 | 6 | true |
| 2 | 2 | create bookmarks indexes | SQL | V2__create_bookmarks_indexes.sql | 732086927 | test | 2023-08-09 09:13:04.456876 | 4 | true |
如果你保持运行相同的数据库实例,并重新启动应用程序,Flyway 不会重新运行已经应用的迁移。如果你添加了新的迁移脚本,那么只有这些迁移脚本会被执行。
Flyway 规则:
必须遵守以下规则,否则 Flyway 将在应用迁移时出错:
- 在 flyway 迁移脚本文件名中不应有重复的版本号。例如:
V1__init.sql
、V1__indexes.sql
。这里多次使用了版本号1
。 - 迁移一旦应用,就不得更改其内容。
自定义 Flyway 配置
Spring Boot 为 Flyway 迁移提供了合理的默认值,但你可以在 application.properties
文件中使用 spring.flyway.{property-name}
属性配置各种 Flyway 配置属性。
不同数据库的 Flyway 迁移
如果你正在构建一个可以与不同数据库一起使用的应用,那么你可以按以下方式配置 flyway migration location:
spring.flyway.locations=classpath:db/migration/{vendor}
然后,你可以把 mysql
专用脚本放在 src/main/resources/db/migration/mysql
目录下,把 postgresql
专用脚本放在 src/main/resources/db/migration/postgresql
目录下,等等。你可以在 org.springframework.boot.jdbc.DatabaseDriver
类中查看更多的数据库供应商名称。
除此之外,还有一些其他的 flyway properties 配置:
# 禁止flyway 执行
spring.flyway.enabled=false
# 如果你有一个现有的数据库,并打算开始使用 Flyway 进行新的数据库更改。
spring.flyway.baseline-on-migrate=true
# 自定义 flyway 迁移跟踪表名称
spring.flyway.table=db_migrations
# 如果出现执行错误,清空数据库并重新运行所有迁移操作
# 切勿在生产中使用。仅适用于开发!!!
spring.flyway.clean-disabled=false
spring.flyway.clean-on-validation-error=true
基于 Java 的 Flyway 迁移
除了 SQL 脚本,还可以使用 Java 以为编程式编写数据库迁移。在 db.migration
包中创建 V3__InsertSampleData.java
方法,如下:
package db.migration;
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
public class V3__InsertSampleData extends BaseJavaMigration {
public void migrate(Context context) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(
new SingleConnectionDataSource(context.getConnection(), true));
Long userId = jdbcTemplate.query("...");
// insert roles for userId
jdbcTemplate.update("...");
}
}
如果数据库更改涉及复杂的逻辑,而使用普通 SQL 又难以编写,那么基于 Java 的迁移就会非常方便。
你可以将 Flyway 与不同的持久化技术结合使用,如 JdbcTemplate
、Spring Data Jdbc
、Spring Data JPA
、jOOQ
等。
总结
Spring Boot 对 Flyway 开箱即用的支持使得非常容易就可以实现生产级数据库迁移。
参考:https://www.sivalabs.in/spring-boot-flyway-database-migration-tutorial/