数据库初始化

SQL 数据库的初始化方式取决于你的技术栈。 当然,如果数据库是独立进程,也可以手动初始化。 推荐使用单一机制进行模式(schema)生成。

使用 Hibernate 初始化数据库

你可以通过设置 spring.jpa.hibernate.ddl-auto 控制 Hibernate 的数据库初始化。 支持的取值有 nonevalidateupdatecreatecreate-drop。 Spring Boot 会根据你是否使用嵌入式数据库为你选择默认值。 嵌入式数据库通过 Connection 类型和 JDBC url 识别。 hsqldbh2derby 属于嵌入式数据库,其他则不是。 如果检测到嵌入式数据库且未检测到 schema 管理器(Flyway 或 Liquibase),ddl-auto 默认值为 create-drop。 其他情况下,默认为 none

从内存数据库切换到“真实”数据库时,请注意不要假设新平台上表和数据一定存在。 你要么显式设置 ddl-auto,要么使用其他机制初始化数据库。

你可以通过启用 org.hibernate.SQL logger 输出 schema 创建语句。 如果你启用了 debug 模式,系统会自动为你完成此操作。

此外,如果 Hibernate 从零创建 schema(即 ddl-auto 属性为 createcreate-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-locationsspring.sql.init.data-locations 自定义。 optional: 前缀表示即使文件不存在,应用也会启动。 如果希望文件缺失时应用启动失败,移除 optional: 前缀即可。

此外,Spring Boot 还会处理 optional:classpath*:schema-${platform}.sqloptional:classpath*:data-${platform}.sql 文件(如存在),其中 ${platform} 取自 spring.sql.init.platform。 这样可以根据需要切换为数据库专用脚本。 例如,你可以将其设置为数据库厂商名(如 hsqldbh2oraclemysqlpostgresql 等)。

默认情况下,只有使用嵌入式内存数据库时才会执行 SQL 数据库初始化。 如需无论数据库类型都初始化,设置 spring.sql.init.modealways。 如需禁用初始化,设置 spring.sql.init.modenever。 默认情况下,Spring Boot 启用了基于脚本的数据库初始化器的 fail-fast 特性。 即如果脚本执行异常,应用启动会失败。 你可以通过设置 spring.sql.init.continue-on-error 调整该行为。

基于脚本的 DataSource 初始化默认在任何 JPA EntityManagerFactory bean 创建之前执行。 schema.sql 可用于为 JPA 管理的实体创建 schema,data.sql 可用于填充数据。 虽然我们不推荐同时使用多种数据源初始化技术,但如果你希望基于脚本的 DataSource 初始化能在 Hibernate 创建 schema 后继续构建,可将 spring.jpa.defer-datasource-initialization 设为 true。 这样会延迟数据源初始化,直到所有 EntityManagerFactory bean 创建并初始化完成。 此时 schema.sql 可用于对 Hibernate 创建的 schema 进行补充,data.sql 可用于填充数据。

初始化脚本支持 -- 单行注释和 /* */ 块注释。 不支持其他注释格式。

如果你使用 高级数据库迁移工具(如 Flyway 或 Liquibase),应仅用它们来创建和初始化 schema。 不推荐将基础的 schema.sqldata.sql 脚本与 Flyway 或 Liquibase 混用,且未来版本将移除该支持。

如需用高级数据库迁移工具初始化测试数据,请参见 FlywayLiquibase 相关章节。

初始化 Spring Batch 数据库

如果你使用 Spring Batch,框架已为大多数主流数据库平台预置了 SQL 初始化脚本。 Spring Boot 能自动检测你的数据库类型并在启动时执行这些脚本。 如果使用嵌入式数据库,默认会自动初始化。 你也可以为任意数据库类型启用,如下例所示:

  • Properties

  • YAML

spring.batch.jdbc.initialize-schema=always
spring:
  batch:
    jdbc:
      initialize-schema: "always"

你也可以通过将 spring.batch.jdbc.initialize-schema 显式设置为 never 来关闭初始化。

使用高级数据库迁移工具

Spring Boot 支持两种高级迁移工具: FlywayLiquibase

启动时执行 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> 为下划线分隔的版本号,如 12_1)。 默认目录为 classpath:db/migration,可通过设置 spring.flyway.locations 修改。 该属性为逗号分隔的一个或多个 classpath:filesystem: 路径。 例如,以下配置会在默认 classpath 目录和 /opt/migration 目录查找脚本:

  • Properties

  • YAML

spring.flyway.locations=classpath:db/migration,filesystem:/opt/migration
spring:
  flyway:
    locations: "classpath:db/migration,filesystem:/opt/migration"

你还可以添加特殊的 {vendor} 占位符以使用数据库专用脚本。 假设如下配置:

  • Properties

  • YAML

spring.flyway.locations=classpath:db/migration/{vendor}
spring:
  flyway:
    locations: "classpath:db/migration/{vendor}"

上述配置会根据数据库类型(如 MySQL 为 db/migration/mysql)选择目录。 支持的数据库列表见 DatabaseDriver

迁移也可用 Java 编写。 Flyway 会自动配置实现了 JavaMigration 的 bean。

FlywayProperties 提供了大部分 Flyway 设置及少量额外属性,可用于禁用迁移或关闭位置检查。 如需更细致的配置,可注册 FlywayConfigurationCustomizer bean。

Spring Boot 通过调用 Flyway.migrate() 执行数据库迁移。 如需更大控制权,可提供实现 FlywayMigrationStrategy@Bean

Flyway 支持 SQL 和 Java 回调。 如用 SQL 回调,将脚本放在 classpath:db/migration 目录。 如用 Java 回调,实现 Callback 的 bean 即可。 这些 bean 会自动注册到 Flyway。 可通过 @Order 注解或实现 Ordered 排序。

默认情况下,Flyway 会自动装配上下文中的(@PrimaryDataSource 并用于迁移。 如需使用其他 DataSource,可自行创建并用 @Bean 标记为 @FlywayDataSource。 如需保留主自动配置的数据源并共存,记得将 @Bean 注解的 defaultCandidate 属性设为 false。 或者,也可通过在外部属性中设置 spring.flyway.[url,user,password] 使用 Flyway 的原生 DataSource。 只要设置了 spring.flyway.urlspring.flyway.user,Flyway 就会使用自己的 DataSource。 如三者有未设置,则会使用对应的 spring.datasource 属性值。

你还可以用 Flyway 针对特定场景提供数据。 例如,将测试专用迁移脚本放在 src/test/resources,仅在测试启动时运行。 也可用 profile 专属配置自定义 spring.flyway.locations,使某些迁移仅在特定 profile 激活时运行。 如在 application-dev.properties 中可指定:

  • 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。

当你将 org.liquibase:liquibase-core 加入 classpath 后,数据库迁移会在应用启动和测试前自动执行。 可通过 spring.liquibase.enabled 属性分别在 maintest 配置中设置不同值进行自定义。 不能同时用两种方式初始化数据库(如应用启动用 Liquibase,测试用 JPA)。

默认情况下,主变更日志读取自 db/changelog/db.changelog-master.yaml,可通过设置 spring.liquibase.change-log 修改。 除了 YAML,Liquibase 还支持 JSON、XML 和 SQL 变更日志格式。

默认情况下,Liquibase 会自动装配上下文中的(@PrimaryDataSource 并用于迁移。 如需使用其他 DataSource,可自行创建并用 @Bean 标记为 @LiquibaseDataSource。 如需保留主自动配置的数据源并共存,记得将 @Bean 注解的 defaultCandidate 属性设为 false。 或者,也可通过在外部属性中设置 spring.liquibase.[driver-class-name,url,user,password] 使用 Liquibase 的原生 DataSource。 只要设置了 spring.liquibase.urlspring.liquibase.user,Liquibase 就会使用自己的 DataSource。 如三者有未设置,则会使用对应的 spring.datasource 属性值。

更多可用设置(如 contexts、默认 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 文件中添加如下属性:

  • 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:
        # 在此插入你的变更

该 changelog 会在测试运行时被使用,不会被打包进 uber jar 或容器。 它包含生产 changelog,并声明一个新的 changeset,其中 runOrder: last 表示该 changeset 会在所有生产 changeset 之后执行。 你可以使用 insert changeset 插入数据,或用 sql changeset 直接执行 SQL。

最后,需要配置 Spring Boot 在运行测试时激活 test profile。 可在带有 @SpringBootTest 注解的测试类上添加 @ActiveProfiles("test") 注解。

依赖已初始化的数据库

数据库初始化作为应用上下文刷新的一部分,在应用启动时执行。 为便于在启动期间访问已初始化的数据库,作为数据库初始化器的 bean 以及依赖数据库已初始化的 bean 会被自动检测。 依赖数据库初始化的 bean 会被配置为依赖于初始化数据库的 bean。 如果应用在启动期间尝试访问尚未初始化的数据库,可以配置额外的 bean 检测机制,以确保初始化顺序。

检测数据库初始化器

Spring Boot 会自动检测以下类型的 bean 作为 SQL 数据库初始化器:

如果你使用了第三方数据库初始化库的 starter,可能会自动检测其他类型的 bean。 如需检测其他 bean,可在 META-INF/spring.factories 中注册 DatabaseInitializerDetector 的实现。

检测依赖数据库初始化的 Bean

Spring Boot 会自动检测以下类型的 bean 作为依赖数据库初始化的 bean:

如果你使用了第三方数据访问库的 starter,可能会自动检测其他类型的 bean。 如需检测其他 bean,可在 META-INF/spring.factories 中注册 DependsOnDatabaseInitializationDetector 的实现。 或者,也可以在 bean 的类或其 @Bean 方法上添加 @DependsOnDatabaseInitialization 注解。