Published on

Using Flyway Java Migrations with Spring Boot

Authors

Flyway is a tool which is used for database migrations. Basically it lets you version control your database changes with your code. There are other tools like Liquibase but having used both I really appreciate and prefer Flyway's simplicity.

By default Flyway looks on your class path for a package/directory db\migration and runs SQL scripts from there. This will cover you for the majority of DB related tasks but if you find you need something more advanced you can use Flyway's Java migration scripts. The problem with this is even though there is a SpringJdbcMigration interface which you can implement to use this with Spring, in practice it does not work as you would expect - you cannot wire in beans and use them.

After doing a bit of digging I came across this and this. Based on these sources I configured my project as below.

Spring Utility Class as per here

package com.avehlies.springbeansflyway;

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

}

Spring Bean Config

    @Bean(initMethod = "migrate")
    @DependsOn("springUtility")
    public Flyway flyway() {
        Flyway flyway = new Flyway();
        flyway.setBaselineOnMigrate(true);
        flyway.setDataSource(dataSource);
        return flyway;
    }

The key thing here is the @DependsOn annotation in the Spring Bean config which forces this bean to only load once a bean called springUtility has loaded (this matches the name of the component in the first code listing). Finally your Java Spring migration would look something like the below:

package db.migration;
...
import org.flywaydb.core.api.migration.spring.SpringJdbcMigration;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class V7__Some_Complex_Migration implements SpringJdbcMigration {

       private MyComplexService myComplexService = SpringUtility.getBean(MyComplexService.class);

       private MyComplexRepo myComplexRepo = SpringUtility.getBean(MyComplexRepo.class);

        @Override
        public void migrate(JdbcTemplate jdbcTemplate) {
               ...
               myComplexService.doYourThing();
               ...
               myComplexRepo.save(something);
               ...

        }

In the above example the db.migration package was used. You can use any package but Flyway by default uses that so it makes your life much easier to just stick to that for your SQL and Java based migrations.

These Java migrations open up a whole lot of possibilities as they can run code using beans you need wired in for DB changes. But this does not need to be limited to DB only migrations. There is nothing stopping you from writing Java migrations for things you only want to run once per deployment.