数据库初始化
SQL 数据库可以通过不同的方式进行初始化,具体取决于你的技术栈。当然,如果数据库是一个独立的进程,你也可以手动进行初始化。建议仅使用一种机制来生成 schema。
使用 Hibernate 初始化数据库
你可以设置 spring.jpa.hibernate.ddl-auto
来控制 Hibernate 的数据库初始化。支持的值包括 none
、validate
、update
、create
和 create-drop
。Spring Boot 会根据你是否使用嵌入式数据库为你选择一个默认值。嵌入式数据库通过查看 Connection
类型和 JDBC URL 来识别。hsqldb
、h2
或 derby
是嵌入式数据库,其他则不是。如果识别出嵌入式数据库且未检测到 schema 管理器(Flyway 或 Liquibase),则 ddl-auto
默认值为 create-drop
。在所有其他情况下,默认值为 none
。
从内存数据库切换到“真实”数据库时要小心,不要对新平台中表和数据的存在做出假设。你必须显式设置 ddl-auto
或使用其他机制之一来初始化数据库。
你可以通过启用 org.hibernate.SQL 日志记录器来输出 schema 创建过程。如果你启用了调试模式,这将自动为你完成。 |
此外,如果在 Hibernate 从头开始创建 schema(即 ddl-auto
属性设置为 create
或 create-drop
)时,classpath 根目录下的 import.sql
文件会在启动时执行。如果你小心谨慎,这对于演示和测试可能很有用,但在生产环境中,你可能不希望它出现在 classpath 中。这是 Hibernate 的一个特性(与 Spring 无关)。
使用基本 SQL 脚本初始化数据库
Spring Boot 可以自动创建你的 JDBC DataSource
或 R2DBC ConnectionFactory
的 schema(DDL 脚本)并初始化其数据(DML 脚本)。
默认情况下,它从 optional:classpath*:schema.sql
加载 schema 脚本,从 optional:classpath*:data.sql
加载数据脚本。这些 schema 和数据脚本的位置可以分别使用 spring.sql.init.schema-locations
和 spring.sql.init.data-locations
进行定制。optional:
前缀表示即使文件不存在,应用也会启动。要让应用在文件不存在时启动失败,请移除 optional:
前缀。
此外,Spring Boot 会处理 optional:classpath*:schema-${platform}.sql
和 optional:classpath*:data-${platform}.sql
文件(如果存在),其中 ${platform}
是 spring.sql.init.platform
的值。这允许你在必要时切换到特定于数据库的脚本。例如,你可以选择将其设置为数据库的供应商名称(如 hsqldb
、h2
、oracle
、mysql
、postgresql
等等)。
默认情况下,SQL 数据库初始化仅在使用嵌入式内存数据库时执行。要始终初始化 SQL 数据库,无论其类型如何,请将 spring.sql.init.mode
设置为 always
。类似地,要禁用初始化,请将 spring.sql.init.mode
设置为 never
。默认情况下,Spring Boot 启用其基于脚本的数据库初始化器的快速失败特性。这意味着,如果脚本导致异常,应用将无法启动。你可以通过设置 spring.sql.init.continue-on-error
来调整此行为。
默认情况下,基于脚本的 DataSource
初始化是在创建任何 JPA EntityManagerFactory
beans 之前执行的。schema.sql
可用于创建 JPA 管理实体的 schema,data.sql
可用于填充数据。虽然我们不建议使用多种数据源初始化技术,但如果你希望基于脚本的 DataSource
初始化能够基于 Hibernate 执行的 schema 创建进行,请将 spring.jpa.defer-datasource-initialization
设置为 true
。这将把数据源初始化推迟到任何 EntityManagerFactory
beans 创建和初始化之后。然后,schema.sql
可用于对 Hibernate 执行的任何 schema 创建进行补充,而 data.sql
可用于填充数据。
初始化脚本支持 -- 作为单行注释,支持 /* */ 作为块注释。不支持其他注释格式。 |
如果你正在使用 更高级的数据库迁移工具,例如 Flyway 或 Liquibase,你应该单独使用它们来创建和初始化 schema。不建议将基本的 schema.sql
和 data.sql
脚本与 Flyway 或 Liquibase 一起使用,未来版本将移除对此的支持。
初始化 Spring Batch 数据库
如果你使用 Spring Batch,它预装了适用于大多数流行数据库平台的 SQL 初始化脚本。Spring Boot 可以检测你的数据库类型并在启动时执行这些脚本。如果你使用嵌入式数据库,这会默认发生。你也可以为任何数据库类型启用它,如以下示例所示
-
属性
-
YAML
spring.batch.jdbc.initialize-schema=always
spring:
batch:
jdbc:
initialize-schema: "always"
你也可以通过将 spring.batch.jdbc.initialize-schema
设置为 never
来显式关闭初始化。
使用更高级的数据库迁移工具
在启动时执行 Flyway 数据库迁移
要在启动时自动运行 Flyway 数据库迁移,请将相应的 Flyway 模块添加到你的 classpath 中。内存数据库和文件数据库由 org.flywaydb:flyway-core
支持。否则,需要特定于数据库的模块。例如,与 PostgreSQL 一起使用 org.flywaydb:flyway-database-postgresql
,与 MySQL 一起使用 org.flywaydb:flyway-mysql
。有关更多详细信息,请参阅Flyway 文档。
通常,迁移是形式为 V<VERSION>__<NAME>.sql
的脚本(其中 <VERSION>
是以下划线分隔的版本,例如“1”或“2_1”)。默认情况下,它们位于名为 classpath:db/migration
的目录中,但你可以通过设置 spring.flyway.locations
来修改该位置。这是一个由一个或多个 classpath:
或 filesystem:
位置组成的逗号分隔列表。例如,以下配置将在默认的 classpath 位置和 /opt/migration
目录中搜索脚本
-
属性
-
YAML
spring.flyway.locations=classpath:db/migration,filesystem:/opt/migration
spring:
flyway:
locations: "classpath:db/migration,filesystem:/opt/migration"
你还可以添加一个特殊的 {vendor}
占位符来使用特定于供应商的脚本。假设以下配置
-
属性
-
YAML
spring.flyway.locations=classpath:db/migration/{vendor}
spring:
flyway:
locations: "classpath:db/migration/{vendor}"
前面的配置没有使用 db/migration
,而是根据数据库的类型设置要使用的目录(例如,MySQL 使用 db/migration/mysql
)。支持的数据库列表可在 DatabaseDriver
中找到。
迁移也可以用 Java 编写。Flyway 将自动配置实现 JavaMigration
的任何 bean。
FlywayProperties
提供了 Flyway 的大部分设置以及一小部分额外属性,可用于禁用迁移或关闭位置检查。如果你需要对配置进行更多控制,请考虑注册一个实现 FlywayConfigurationCustomizer
的 bean。
Spring Boot 调用 Flyway.migrate()
执行数据库迁移。如果你想进行更多控制,请提供一个实现 @Bean
并实现 FlywayMigrationStrategy
的 bean。
Flyway 支持 SQL 和 Java 回调。要使用基于 SQL 的回调,请将回调脚本放在 classpath:db/migration
目录中。要使用基于 Java 的回调,请创建一个或多个实现 Callback
的 bean。任何此类 bean 都会自动注册到 Flyway
中。它们可以使用 @Order
或实现 Ordered
来进行排序。
默认情况下,Flyway 会自动注入上下文中的 (@Primary
) DataSource
并将其用于迁移。如果你想使用不同的 DataSource
,可以创建一个并将其 @Bean
标记为 @FlywayDataSource
。如果你这样做并且想要两个数据源(例如保留主要的自动配置 DataSource
),请记住将 @Bean
注解的 defaultCandidate
属性设置为 false
。另外,你也可以通过在外部属性中设置 spring.flyway.[url,user,password]
来使用 Flyway 原生的 DataSource
。设置 spring.flyway.url
或 spring.flyway.user
中的任何一个都足以使 Flyway 使用自己的 DataSource
。如果这三个属性中的任何一个未设置,则将使用其对应的 spring.datasource
属性的值。
你还可以使用 Flyway 为特定场景提供数据。例如,你可以将测试专用的迁移放在 src/test/resources
中,并且它们只在你的应用启动进行测试时运行。此外,你可以使用特定于 profile 的配置来定制 spring.flyway.locations
,以便某些迁移仅在特定 profile 激活时运行。例如,在 application-dev.properties
中,你可以指定以下设置
-
属性
-
YAML
spring.flyway.locations=classpath:/db/migration,classpath:/dev/db/migration
spring:
flyway:
locations: "classpath:/db/migration,classpath:/dev/db/migration"
通过这种设置,dev/db/migration
中的迁移仅在 dev
profile 激活时运行。
在启动时执行 Liquibase 数据库迁移
要在启动时自动运行 Liquibase 数据库迁移,请将 org.liquibase:liquibase-core
添加到你的 classpath 中。
将 |
默认情况下,主 change log 从 db/changelog/db.changelog-master.yaml
读取,但你可以通过设置 spring.liquibase.change-log
来更改位置。除了 YAML,Liquibase 还支持 JSON、XML 和 SQL change log 格式。
默认情况下,Liquibase 会自动注入上下文中的 (@Primary
) DataSource
并将其用于迁移。如果你需要使用不同的 DataSource
,可以创建一个并将其 @Bean
标记为 @LiquibaseDataSource
。如果你这样做并且想要两个数据源(例如保留主要的自动配置 DataSource
),请记住将 @Bean
注解的 defaultCandidate
属性设置为 false
。另外,你也可以通过在外部属性中设置 spring.liquibase.[driver-class-name,url,user,password]
来使用 Liquibase 原生的 DataSource
。设置 spring.liquibase.url
或 spring.liquibase.user
中的任何一个都足以使 Liquibase 使用自己的 DataSource
。如果这三个属性中的任何一个未设置,则将使用其对应的 spring.datasource
属性的值。
有关可用设置的详细信息,例如上下文、默认 schema 等,请参阅 LiquibaseProperties
。
如果你想在使用 Liquibase
实例之前对其进行定制,你也可以使用 Customizer<Liquibase>
bean。
将 Flyway 用于仅测试迁移
如果你想创建用于填充测试数据库的 Flyway 迁移,请将它们放在 src/test/resources/db/migration
中。例如,名为 src/test/resources/db/migration/V9999__test-data.sql
的文件将在生产迁移之后执行,并且仅在你运行测试时执行。你可以使用此文件来创建所需的测试数据。此文件不会打包到你的 uber jar 或容器中。
将 Liquibase 用于仅测试迁移
如果你想创建用于填充测试数据库的 Liquibase 迁移,你必须创建一个包含生产 changelog 的测试 changelog。
首先,你需要配置 Liquibase 在运行测试时使用不同的 changelog。一种方法是创建一个 Spring Boot 的 test
profile,并将 Liquibase 属性放在其中。为此,创建一个名为 src/test/resources/application-test.properties
的文件,并在其中放入以下属性
-
属性
-
YAML
spring.liquibase.change-log=classpath:/db/changelog/db.changelog-test.yaml
spring:
liquibase:
change-log: "classpath:/db/changelog/db.changelog-test.yaml"
这将配置 Liquibase 在 test
profile 中运行时使用不同的 changelog。
现在在 src/test/resources/db/changelog/db.changelog-test.yaml
创建 changelog 文件
databaseChangeLog:
- include:
file: classpath:/db/changelog/db.changelog-master.yaml
- changeSet:
runOrder: "last"
id: "test"
changes:
# Insert your changes here
此 changelog 将在测试运行时使用,并且不会打包到你的 uber jar 或容器中。它包含生产 changelog,然后声明一个新的 changeset,其 runOrder: last
设置指定它在所有生产 changesets 运行后运行。你现在可以使用例如 insert changeset 插入数据,或者使用 sql changeset 直接执行 SQL。
最后要做的是配置 Spring Boot 在运行测试时激活 test
profile。为此,你可以将 @ActiveProfiles("test")
注解添加到你的带有 @SpringBootTest
注解的测试类中。
依赖于已初始化的数据库
数据库初始化在应用启动时作为应用上下文刷新的一部分执行。为了允许在启动期间访问已初始化的数据库,充当数据库初始化器和需要数据库已初始化的 bean 会被自动检测到。其初始化依赖于数据库已初始化的 bean 会被配置为依赖于初始化它的 bean。如果在启动期间,你的应用尝试访问数据库而数据库尚未初始化,你可以配置额外检测初始化数据库并需要数据库已初始化的 bean。
检测数据库初始化器
Spring Boot 将自动检测初始化 SQL 数据库的以下类型的 bean
如果你正在使用第三方数据库初始化库的 starter,它可能会提供一个检测器,以便自动检测其他类型的 bean。要让其他 bean 被检测到,请在 META-INF/spring.factories
中注册一个 DatabaseInitializerDetector
的实现。
检测依赖于数据库初始化的 bean
Spring Boot 将自动检测依赖于数据库初始化的以下类型的 bean
-
AbstractEntityManagerFactoryBean
(除非spring.jpa.defer-datasource-initialization
设置为true
) -
DSLContext
(jOOQ) -
EntityManagerFactory
(除非spring.jpa.defer-datasource-initialization
设置为true
)
如果你正在使用第三方数据访问库的 starter,它可能会提供一个检测器,以便自动检测其他类型的 bean。要让其他 bean 被检测到,请在 META-INF/spring.factories
中注册一个 DependsOnDatabaseInitializationDetector
的实现。另外,也可以使用 @DependsOnDatabaseInitialization
注解 bean 的类或其 @Bean
方法。