6

Since version 6.*, Flyway supports Spring bean injection into java migration files with JavaMigration interface implemented. Here is my example:

@Component
public class V1_201809261821__some_migration extends BaseJavaMigration {

    @Autowired
    private SomeDAO someDAO;

    @Override
    public void migrate(Context context) throws Exception {
        someDAO.doSomething();
    }
}

When startup, it complains that:

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

   v1_201809261821__some_migration (field private SomeDAO V1_201809261821__some_migration.someDAO)
┌─────┐
|  someDAO (field private org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate someDAO.namedParameterJdbcTemplate)
↑     ↓
|  flywayInitializer defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]
↑     ↓
|  flyway defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]
↑     ↓
|  v1_201809261821__some_migration (field private SomeDAO V1_201809261821__some_migration.someDAO)
└─────┘

It seems that I can't use JdbcTemplate in Java migration files, Flyway's document shows that I can construct my own JdbcTemplate using Context like:

public void migrate(Context context) {
        new JdbcTemplate(new SingleConnectionDataSource(context.getConnection(), true))
                .execute("INSERT INTO test_user (name) VALUES ('Obelix')");
}

But unfortunately I have no control of SomeDAO, it's from another module I can't touch.

Related versions:

  • Flyway: 6.0.6

  • Spring Boot: 2.2.0

Elderry
  • 1,902
  • 5
  • 31
  • 45

4 Answers4

3

This is how Spring Boot decided to integrate Flyway. In the default autoconfiguration, you cannot do anything with the database before the Flyway migration is done. This is a sensible, but opinionated choice.

Take a look at the source code of FlywayAutoConfiguration. There's a rather nasty trick to ensure that noone can use JdbcTemplate before Flyway is ready:

        /**
         * Additional configuration to ensure that {@link JdbcOperations} beans depend on
         * the {@code flywayInitializer} bean.
         */
        @Configuration
        @ConditionalOnClass(JdbcOperations.class)
        @ConditionalOnBean(JdbcOperations.class)
        protected static class FlywayInitializerJdbcOperationsDependencyConfiguration
                extends JdbcOperationsDependsOnPostProcessor {

            public FlywayInitializerJdbcOperationsDependencyConfiguration() {
                super("flywayInitializer");
            }

        }

To achieve your goals, you will have to disable this autoconfiguration (spring.autoconfigure.exclude) and write the Flyway configuration yourself. You can start with the source code of FlywayAutoConfiguration, but with the tricky methods removed.

However, you will have to add a similar dependency trick, to ensure that your services/jobs are started only after your custom Flyway is ready.

Mateusz Stefek
  • 3,478
  • 2
  • 23
  • 28
1

I was excited about this feature as well, and was very disappointed to find out that it is not possible to autowire classes which in some way are dependent on persistence layer. But the solution described in Flyway Spring Boot Autowired Beans with JPA Dependency still works:

First extend FlywayConfiguration:

@Configuration
@ComponentScan
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true)
class DatabaseMigrationConfiguration extends FlywayConfiguration {

    @Override
    public Flyway flyway(FlywayProperties properties, DataSourceProperties dataSourceProperties,
        ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource,
        ObjectProvider<DataSource> flywayDataSource,
        ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
        ObjectProvider<JavaMigration> javaMigrations,
        ObjectProvider<Callback> callbacks) {
        return super.flyway(properties, dataSourceProperties, resourceLoader, dataSource, flywayDataSource, fluentConfigurationCustomizers,
            javaMigrations, callbacks);
    }

    @Primary
    @Bean(name = "flywayInitializer")
    @DependsOn({ "springUtility" })
    @ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true)
    public FlywayMigrationInitializer flywayInitializer(Flyway flyway,
        ObjectProvider<FlywayMigrationStrategy> migrationStrategy) {
        return super.flywayInitializer(flyway, migrationStrategy);
    }

Second, create this Class to get a Bean from application context:

@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
     */
    static <T> T getBean(final Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

Now you can use this class in your java migration class (extends BaseJavaMigration) to get any Bean you want.

0

Did not look down the stack, but I guess Flyway as a migration tool does not want you to have the data structured while changing it...

Other (non data access) services are injected and can be used.

You get the database connection from the context to change it using jdbc template.

NoamK
  • 61
  • 1
0

In my case,

  1. I used @Lazy annotation to make related beans lazily initialized,
  2. add spring config :allow-circular-references: true to applition yml then flyway can start and process successfully.

And this is what I found why and how flyway cause cycle references:

Like the above answers said, Flyway migrate supports changing table structure DDLs so does not want you to inject jpa beans when doing migrate.

Spring (my version 2.7.9) has FlywayAutoConfiguration annotated with @Import(DatabaseInitializationDependencyConfigurer.class), the generated datasource config will depends on flyway and flywayInitlizer, and make datasource initialization must happens after flyway completed.

But sometimes we need this to be able to do things like insert or update data(my case) in java code. with the help of flyway, we can benefit of both auto sql migrate and customized java code.

ronkou
  • 1