user3636486
user3636486

Reputation: 429

Spring Boot: Hibernate and Flyway boot order

I have created Spring application. Pom xml is attached.

It has a config like this (below) and some db/migration/V1__init.sql for Flyway db migration tool.

It has hsqldb in-memory database and it is created after application is started. It is clean after creation.

I want Hibernate to create a schema based on entity classes and then Flyway fills the tables. Now Flyway starts V1__init.sql before tables is created and throw an exception. How can I change this order or what solution can I do?

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>

Upvotes: 33

Views: 36339

Answers (7)

Toribio
Toribio

Reputation: 4078

For anyone who still wants to configure Flyway as a bean as of Spring Boot 2.1.8 and later, but is falling onto the circular dependency error, there is a way to exclude the @DependsOn("flyway") that the auto-configured EntityManagerFactory uses by default.

First, set spring.flyway.enabled to false and then create a class extending BeanFactoryPostProcessor so we can exclude the Flyway dependency before the beans are created. Example:

@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)));
    }
}

This example is very minimal and could probably break on new versions. For instance, in my project, I did the same thing somewhat differently using a custom method recursivelyExcludeDependencyOnFromBeanDefinition(EntityManagerFactory.class, Flyway.class), that finds all dependencies form EntityManagerFactory which also depends on Flyway, so we don't have to know, for example, that DataSourceScriptDatabaseInitializer also depends on Flyway.

To make it all work, just add the bean definition normally:

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

Of course, if you don't care about Flyway being available as a bean, then I would recommend using a custom @Configuration file like the one from @pavelety's answer.

Upvotes: 0

Amr Mostafa
Amr Mostafa

Reputation: 21

This works for me with Spring Boot 2.3.1

1- you need to disable boot for Flyway, by insert into resources/application.properties:

spring.flyway.enabled = false

2- Start your migration scripts from version 2 as V1 is used as a baseline, V1 file will be ignored.

3- Make the @SpringBootApplication class implements CommandLineRunner interface (It is used to execute the code after the Spring Boot application started ). and it will be like this:

@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

Upvotes: 2

pavelety
pavelety

Reputation: 874

All SQL migrations will start after Hibernate creates all the tables.

Spring Boot 2.2.2, Flyway 6.0.8

To disable boot for Flyway, insert into resources/application.properties:

spring.flyway.enabled=false

Create separate configuration for Flyway to make it load when Hibernate is ready:

@Configuration
public class FlywayConfiguration {

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

Start your migration scripts from version 2:

resources/db.migration/V2__fill-tables.sql

V1 is used as a baseline, V1 file will be ignored.

Upvotes: 24

peterzinho16
peterzinho16

Reputation: 989

For more recent users that use Spring Boot +2.1 and as @mota commented into @user3707816's answer, you can use spring.flyway.enabled=false in application.properties and then create a new instance manually:

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

Upvotes: 3

may be because of the order

add on application.properties

flyway.out-of-order = true

or application.yml on spring

flyway:
  out-of-order: true

Upvotes: -1

user3707816
user3707816

Reputation: 300

I had the same issue.

I wanted my schema to be created by hibernate because of it's database independence. I already went through the trouble of figuring out a nice schema for my application in my jpa classes, I don't like repeating myself.

But I want some data initialization to be done in a versioned manner which flyway is good at.

Spring boot runs flyway migrations before hibernate. To change it I overrode the spring boot initializer to do nothing. Then I created a second initializer that runs after hibernate is done. All you need to do is add this configuration class:

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);
    }


}

That code needs java 8, If you have java 7 or earlier, replace (f)->{} with an inner class that implements FlywayMigrationStrategy

Of course you can do this in xml just as easily.

Make sure to add this to your application.properties:

flyway.baselineOnMigrate = true

Upvotes: 30

Andy Wilkinson
Andy Wilkinson

Reputation: 116091

Spring Boot auto-configuration of Flyway ensures that database migrations have run before Hibernate is initialised. In other words, you can't rely on Flyway auto-configuration and use Flyway to populate tables created by Hinernate.

One solution is to fully embrace Flyway and use it to both create the tables and populate them. You can then switch off Hibernate's table creation (spring.jpa.hibernate.ddl-auto=none). This approach is more robust as it will allow your database to evolve more easily. This is what I would recommend that you do.

Another solution is to disable auto-configuration of Flyway (flyway.enabled=false) and to configure it your self. You can then configure Flyway to depend on Hibernate so that Hibernate has created the tables before Flyway tries to populate them.

Upvotes: 19

Related Questions