Flyway Spring Boot Autowired Beans with JPA Dependency

I struggled with this for a long time due to my JPA dependency. I am going to edit the title of my question slightly to reflect this...

@Autowired beans are instantiated from the ApplicationContext. We can create a different bean that is ApplicationContextAware and use that to "manually wire" our beans for use in migrations.

A quite clean approach can be found here. Unfortunately, this throws an uncaught exception (specifically, ApplicationContext is null) when using JPA. Luckily, we can solve this by using the @DependsOn annotation and force flyway to run after the ApplicationContext has been set.

First we'll need the SpringUtility from avehlies/spring-beans-flyway2 above.

package com.mypackage;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringUtility implements ApplicationContextAware {

    @Autowired
    private static ApplicationContext applicationContext;

    public void setApplicationContext(final ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /*
        Get a class bean from the application context
     */
    public static <T> T getBean(final Class clazz) {
        return (T) applicationContext.getBean(clazz);
    }

    /*
        Return the application context if necessary for anything else
     */
    public static ApplicationContext getContext() {
        return applicationContext;
    }

}

Then, configure a flywayInitializer with a @DependsOn for springUtility. I extended the FlywayAutoConfiguration here hoping to keep the autoconfiguration functionality. This mostly seems to have worked for me, except that turning off flyway in my gradle.build file no longer works, so I had to add the @Profile("!integration") to prevent it from running during my tests. Other than that the autoconfiguration seems to work for me but admittedly I've only run one migration. Hopefully someone will correct me if I am wrong.

package com.mypackage;

import org.flywaydb.core.Flyway;
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializer;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayConfiguration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.DependsOn;

import com.mypackage.SpringUtility;

@Configuration
@Profile("!integration")
class MyFlywayConfiguration extends FlywayConfiguration {
    @Primary
    @Bean(name = "flywayInitializer")
    @DependsOn("springUtility")
    public FlywayMigrationInitializer flywayInitializer(Flyway flyway){
        return super.flywayInitializer(flyway);
        //return new FlywayMigrationInitializer(flyway, null);
    }
}

And just to complete the example, here is a migration:

package db.migration;

import org.flywaydb.core.api.migration.spring.BaseSpringJdbcMigration;
import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.stereotype.Component;

import com.mypackage.repository.AccountRepository;
import com.mypackage.domain.Account;

import com.mypackage.SpringUtility;

import java.util.List;

public class V2__account_name_ucase_firstname extends BaseSpringJdbcMigration {

    private AccountRepository accountRepository = SpringUtility.getBean(AccountRepository.class);

    public void migrate(JdbcTemplate jdbcTemplate) throws Exception {

        List<Account> accounts = accountRepository.findAll();

        for (Account account : accounts) {

            String firstName = account.getFirstName();
            account.setFirstName(firstName.substring(0, 1).toUpperCase() + firstName.substring(1));
            account = accountRepository.save(account);

        }
    }
}

Thanks to avehlies on github, Andy Wilkinson on stack overflow and OldIMP on github for helping me along the way.

In case you are using more recent versions of Flyway, then extend BaseJavaMigration instead of BaseSpringJdbcMigration as the later is deprecated. Also, take a look at the below two comments by the user Wim Deblauwe.


The functionality hasn't made it into Flyway yet. It's being tracked by this issue. At the time of writing that issue is open and assigned to the 5.1.0 milestone.