【问题标题】:Spring Boot: Hibernate and Flyway boot orderSpring Boot:Hibernate 和 Flyway 启动顺序
【发布时间】:2016-09-03 00:46:15
【问题描述】:

我已经创建了 Spring 应用程序。附上Pom xml。

它有这样的配置(如下)和一些用于 Flyway db 迁移工具的 db/migration/V1__init.sql。

它有 hsqldb 内存数据库,它是在应用程序启动后创建的。创建后是干净的。

我希望 Hibernate 基于实体类创建一个模式,然后 Flyway 填充表。现在 Flyway 在创建表之前启动 V1__init.sql 并抛出异常。我该如何更改此顺序或我可以采取什么解决方案?

spring.datasource.testWhileIdle = true
spring.datasource.validationQuery = SELECT 1
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = create-drop
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.HSQLDialect

pom.xml:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.3.RELEASE</version>
    <relativePath/>
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>1.3.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.hsqldb</groupId>
        <artifactId>hsqldb</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>4.3.11.Final</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>4.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring4</artifactId>
        <version>2.1.4.RELEASE</version>
    </dependency>

    <!-- For using 'LEGACYHTML5' mode in Thymeleaf -->
    <dependency>
        <groupId>net.sourceforge.nekohtml</groupId>
        <artifactId>nekohtml</artifactId>
        <version>1.9.21</version>
    </dependency>
    <dependency>
        <groupId>xml-apis</groupId>
        <artifactId>xml-apis</artifactId>
        <version>1.4.01</version>
    </dependency>

    <dependency>
        <groupId>org.flywaydb</groupId>
        <artifactId>flyway-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
        <version>1.3.3.RELEASE</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

【问题讨论】:

  • 与您的问题无关,但您应该从 pom 中删除许多版本并让 Boot 为您配置它们。而且你当然不应该混合 Spring Boot 本身的版本(你有 1.3.2.RELEASE 和 1.3.3.RELEASE)。

标签: java hibernate spring-boot spring-data-jpa flyway


【解决方案1】:

我遇到了同样的问题。

我希望我的架构由 hibernate 创建,因为它具有数据库独立性。我已经在我的 jpa 课程中为我的应用程序找到了一个很好的架构,我不喜欢重复自己。

但我希望以 flyway 擅长的版本化方式完成一些数据初始化。

Spring boot 在休眠之前运行 flyway 迁移。为了改变它,我覆盖了 spring boot 初始化程序,什么也不做。然后我创建了第二个在休眠完成后运行的初始化程序。您需要做的就是添加这个配置类:

import org.flywaydb.core.Flyway;
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

@Configuration
public class MigrationConfiguration {


    /**
     * Override default flyway initializer to do nothing
     */
    @Bean
    FlywayMigrationInitializer flywayInitializer(Flyway flyway) {
        return new FlywayMigrationInitializer(flyway, (f) ->{} );
    }


    /**
     * Create a second flyway initializer to run after jpa has created the schema
     */
    @Bean
    @DependsOn("entityManagerFactory")
    FlywayMigrationInitializer delayedFlywayInitializer(Flyway flyway) {
        return new FlywayMigrationInitializer(flyway, null);
    }


}

该代码需要 java 8,如果您有 java 7 或更早版本,请将 (f)-&gt;{} 替换为实现 FlywayMigrationStrategy 的内部类

当然,您可以在 xml 中轻松做到这一点。

确保将其添加到您的 application.properties:

flyway.baselineOnMigrate = true

【讨论】:

  • 这很好用。而不是@DependsOn,我更喜欢只声明一个参数(然后它是强类型的),但是这种对飞行路径初始化的覆盖非常有用。谢谢!
  • 很好,但我试过了,它无法通过名称找到我的 entitymanagerfactory。
  • 你是否在 pom.xml 中添加了 spring data jpa?检查自动配置在做什么。或者像 Paul Hilliar 所说的按类型注入
  • 这不再适用于 Spring Boot 2.1.8。从 2.1.7 升级到 2.1.8 后,应用无法启动并报错:'delayedFlywayInitializer' 和 'entityManagerFactory' 之间的循环依赖关系
  • @Kikin-Sama 我不得不关闭自动配置 spring.flyway.enabled=false 并手动创建新实例 Flyway.configure().dataSource(dataSource).load();
【解决方案2】:

Flyway 的 Spring Boot 自动配置确保数据库迁移在 Hibernate 初始化之前已经运行。换句话说,您不能依赖 Flyway 自动配置并使用 Flyway 来填充由 Hinernate 创建的表。

一种解决方案是完全采用 Flyway 并使用它来创建表和填充它们。然后,您可以关闭 Hibernate 的表创建 (spring.jpa.hibernate.ddl-auto=none)。这种方法更健壮,因为它可以让您的数据库更轻松地发展。我建议你这样做。

另一个解决方案是禁用 Flyway (flyway.enabled=false) 的自动配置并自行配置。然后,您可以将 Flyway 配置为依赖于 Hibernate,以便 Hibernate 在 Flyway 尝试填充它们之前创建表。

【讨论】:

  • 您有第二个建议的示例代码吗?
  • 我们希望这是真的。实际上 Hibernate 当前首先运行,这是一个未解决的问题。
  • @sofend 我相信你错了,问题也暗示了这一点。我们还在 Boot 中进行了测试,以验证 Flyway 迁移在 Hibernate 之前运行。是什么让您认为 Hibernate 先运行?
  • 嘿,如果是这样,那是个好消息。我有一个我认为是在最新的 Spring Boot 版本(1.4.3.RELEASE)上运行的非常普通的配置。我正在使用最新的测试框架注释。我很高兴分享我的一些配置。或者,如果你已经让它工作了,那么看看你拥有什么并以这种方式学习可能是有意义的。 (您使用的是 Spring Boot,还是直接使用 Spring?)我遇到的问题不是运行 Flyway 来进行数据填充(这是启动此线程的人正在做的事情)。我正在使用 Flyway 迁移架构并希望 Hibernate 对其进行验证。
  • @AndyWilkinson Flyway ensures that database migrations have run before Hibernate is initialised,您是否设法找到验证该语句的特定代码?他们的文档中没有提到这一点
【解决方案3】:

所有 SQL 迁移将在 Hibernate 创建所有表后开始。

Spring Boot 2.2.2,Flyway 6.0.8

要禁用 Flyway 的启动,请插入 resources/application.properties:

spring.flyway.enabled=false

为 Flyway 创建单独的配置,使其在 Hibernate 准备好时加载:

@Configuration
public class FlywayConfiguration {

    @Autowired
    public FlywayConfiguration(DataSource dataSource) {
        Flyway.configure().baselineOnMigrate(true).dataSource(dataSource).load().migrate();
    }
}

从版本 2 开始您的迁移脚本:

resources/db.migration/V2__fill-tables.sql

V1 用作基线,V1 文件将被忽略。

【讨论】:

  • 适用于 Spring Boot 2.6.3 和 8.4.4
  • 你应该得到奖牌
【解决方案4】:

对于最近使用 Spring Boot +2.1 的用户以及 @mota 对 @user3707816 的回答的评论,您可以在 application.properties 中使用 spring.flyway.enabled=false 然后创建一个新实例手动:

Flyway.configure().dataSource(dataSource)
                .baselineOnMigrate(true)
                .schemas(PG_DATABASE_SCHEMA)//optional, by default is public
                .load().migrate();

【讨论】:

  • 做了这个,但在那之后,由于缺少 ServletWebServerFactory bean,无法启动 ServletWebServerApplicationContext。
【解决方案5】:

可能是因为顺序

添加 在 application.properties 上

flyway.out-of-order = true

或 spring 上的 application.yml

flyway:
  out-of-order: true

【讨论】:

  • 这与 Flyway/Hibernate 的执行顺序无关。所以只是为了澄清:此设置将允许迁移无序运行。 IE。 V2__migration.sql 可能会在 V3__update.sql 已应用于数据库之后运行(如果 flyway 检测到它尚未运行)。
【解决方案6】:

这适用于我的 Spring Boot 2.3.1

1- 您需要通过插入 resources/application.properties 来禁用 Flyway 的启动:

spring.flyway.enabled = false

2- 从版本 2 开始迁移脚本,因为 V1 用作基线,V1 文件将被忽略。

3- 使@SpringBootApplication类实现CommandLineRunner接口(用于在Spring Boot应用启动后执行代码 )。它会是这样的:

@SpringBootApplication
public class SpringBootApplication implements CommandLineRunner {

    @Autowired
    DataSource dataSource;

    public static void main(String[] args) {
        SpringApplication.run(SpringBootApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        Flyway.configure().baselineOnMigrate(true).dataSource(dataSource).load().migrate();
    }

}

This answer helped me

【讨论】:

    【解决方案7】:

    对于从 Spring Boot 2.1.8 及更高版本起仍希望将 Flyway 配置为 bean 但陷入循环依赖错误的任何人,有一种方法可以排除 @DependsOn("flyway")自动配置的EntityManagerFactory 默认使用。

    首先,将spring.flyway.enabled 设置为false,然后创建一个扩展BeanFactoryPostProcessor 的类,这样我们就可以在创建bean 之前排除Flyway 依赖项。示例:

    @Component
    public class FlywayDeferrerPostProcessor implements BeanFactoryPostProcessor {
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            excludeDependsOnFlywayFromBean(beanFactory, DataSourceScriptDatabaseInitializer.class);
            excludeDependsOnFlywayFromBean(beanFactory, EntityManagerFactory.class);
        }
    
        private void excludeDependsOnFlywayFromBean(ConfigurableListableBeanFactory beanFactory, Class<?> beanClass) {
            Stream.of(beanFactory.getBeanNamesForType(beanClass))
                .map(beanFactory::getBeanDefinition)
                .filter(it -> it.getDependsOn() != null && it.getDependsOn().length > 0)
                .forEach(it -> it.setDependsOn(Stream.of(it.getDependsOn()).filter(name -> !name.equals("flyway")).toArray(String[]::new)));
        }
    }
    

    此示例非常小,可能会在新版本中中断。例如,在我的项目中,我使用自定义方法 recursivelyExcludeDependencyOnFromBeanDefinition(EntityManagerFactory.class, Flyway.class) 做了同样的事情,这会发现所有依赖关系形式 EntityManagerFactory 也依赖于 Flyway,所以我们不必知道,例如,@ 987654330@ 也依赖于 Flyway。

    要让这一切正常工作,只需正常添加 bean 定义:

    @Bean(initMethod = "migrate")
    @DependsOn("entityManagerFactory")
    public Flyway flyway() { /* ... */ }
    

    当然,如果您不关心 Flyway 是否可以作为 bean 使用,那么我建议您使用自定义的 @Configuration 文件,例如来自 @pavelety's answer 的文件。

    【讨论】:

      猜你喜欢
      • 2021-04-20
      • 2020-08-01
      • 2016-07-26
      • 2020-08-20
      • 2019-11-04
      • 2021-08-19
      • 1970-01-01
      • 2016-10-30
      • 2020-09-12
      相关资源
      最近更新 更多