Mihkel L.
Mihkel L.

Reputation: 1573

Multiple Flyways with FlywayMigrationStrategy

I have multiple Flyway datasources and need to implement FlywayMigrationStrategy to them. Each datasource has their own flyway_migration table etc.

But when I create FlywayMigrationStrategy it will not be called.

This doesn't work:

@Bean
public FlywayMigrationStrategy cleanMigrateStrategy() { ...

This works:

  @PostConstruct
  public void cleanBeforeMigrate(
      @Qualifier("dpaFlyway") Flyway dpaflyway,
      @Qualifier("flyway") Flyway flyway) {

    dpaflyway.clean();
    dpaflyway.migrate();

    flyway.clean();
    flyway.migrate();
  }

Are there better options?

Upvotes: 2

Views: 4491

Answers (1)

Maxim Popov
Maxim Popov

Reputation: 1227

Why do you implement @PostContruct method? As I see, FlywayMigrationStrategy is a functional interface and I guess that your code should look like this:

@Bean
public FlywayMigrationStrategy cleanMigrateStrategy() {
    FlywayMigrationStrategy strategy = new FlywayMigrationStrategy() {
        @Override
        public void migrate(Flyway flyway) {
            flyway.clean();
            flyway.migrate();
        }
    };

    return strategy;
}

Ok, I have some research and now can explain the answer to your question.

First of all, let's check the FlywayAutoConfiguration

When you use configuring flyway migration via a property file, the configuration works, creates and configures Flyway instances and migrates base. All of these are configured in FlywayConfiguration. Let's look on the condition annotation on the configuration

@ConditionalOnMissingBean(Flyway.class)

This means that configuration isn't created if bean for class Flyway.class already exists in the spring context. Ok, next, the configuration creates only to beans

@Bean
public Flyway flyway(FlywayProperties properties, DataSourceProperties dataSourceProperties,
        ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource,
        @FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
        ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
        ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks)
.....
@Bean
@ConditionalOnMissingBean
public FlywayMigrationInitializer flywayInitializer(Flyway flyway,
        ObjectProvider<FlywayMigrationStrategy> migrationStrategy) {
        return new FlywayMigrationInitializer(flyway, migrationStrategy.getIfAvailable());
}

It's not so important how the config configures the flyway bean, but it important that it creates FlywayMigrationInitializer, which initiate migration. As you can see, FlywayMigrationStrategy is set to FlywayMigrationInitializer as constructor argument. And it is he who uses the MigrationStrategy if it was set, otherwise just execute flyway.migrate(). (see source code)

Ok, now we know how it works in general, let's see in your code.

You create to flyway instances in your main configuration:

@Bean(initMethod = "migrate")
@FlywayDataSource
public Flyway firstFlyway(DataSource dataSource) {
  return new Flyway(
      new FluentConfiguration()
          .locations("db/first-migration")
          .schemas("first")
          .outOfOrder(true)
          .dataSource(dataSource)
  );
}

@Bean(initMethod = "migrate")
@FlywayDataSource
public Flyway secondFlyway(@Qualifier("secondDataSource") DataSource dataSource) {
  return new Flyway(
      new FluentConfiguration()
          .dataSource(dataSource)
          .schemas("second")
          .outOfOrder(true)
          .locations("db/second-migration")
  );
}

Since you already create Flyway instances, FlywayConfiguration isn't created( due to the condition) and Initializer beans for your Flyway beans also aren't created. As a result migrations aren't executed and you need to add (initMethod = "migrate") to bean declaration for starting the migration. Also, I think the @FlywayDataSource is not required and do nothing.

Let's now go to your test configuration.

When you creating FlywayMigrationStrategy, it works correctly, but no one use the strategy (in your main config file you call Flyway.migrate just as initMethod and MigrationInitializer isn't created). As a result, the strategy isn't executed.

In your working example, you added calling clean and migrate in TestConfiguration postconstruct method. And it works because postconstruct method is executed after configuration was created. BUT if you debug your code, you will see that migrate method is executed twice for each Flyway instances: as a bean initMethod and from Testconfiguration postcontruct method. I'm not sure that it is what you want.

Ok, for fixing it, I suggest remove (initMethod = "migrate") and @FlywayDataSource, create FlywayMigrationInitializer bean for each Flyway bean in your main configuration and implement FlywayMigrationStrategy in your test config.

@Bean
public FlywayMigrationInitializer flywayInitializer(@Qualifier(...) Flyway flyway,
        ObjectProvider<FlywayMigrationStrategy> migrationStrategy) {
        return new FlywayMigrationInitializer(flyway, migrationStrategy.getIfAvailable());
}

Upvotes: 6

Related Questions