Spring beans are not injected in flyway java based migration

前端 未结 4 1437
迷失自我
迷失自我 2020-12-08 22:13

I\'m trying to inject component of configuration properties in the flyway migration java code but it always null.

I\'m using spring boot with Flyway.



        
4条回答
  •  粉色の甜心
    2020-12-08 22:52

    If like me, you don't want to wait for Flyway 4.1, you can use Flyway 4.0 and add the following to your Spring Boot application:

    1) Create a ApplicationContextAwareSpringJdbcMigrationResolver class in your project:

    import org.flywaydb.core.api.FlywayException;
    import org.flywaydb.core.api.MigrationType;
    import org.flywaydb.core.api.MigrationVersion;
    import org.flywaydb.core.api.configuration.FlywayConfiguration;
    import org.flywaydb.core.api.migration.MigrationChecksumProvider;
    import org.flywaydb.core.api.migration.MigrationInfoProvider;
    import org.flywaydb.core.api.migration.spring.SpringJdbcMigration;
    import org.flywaydb.core.api.resolver.ResolvedMigration;
    import org.flywaydb.core.internal.resolver.MigrationInfoHelper;
    import org.flywaydb.core.internal.resolver.ResolvedMigrationComparator;
    import org.flywaydb.core.internal.resolver.ResolvedMigrationImpl;
    import org.flywaydb.core.internal.resolver.spring.SpringJdbcMigrationExecutor;
    import org.flywaydb.core.internal.resolver.spring.SpringJdbcMigrationResolver;
    import org.flywaydb.core.internal.util.ClassUtils;
    import org.flywaydb.core.internal.util.Location;
    import org.flywaydb.core.internal.util.Pair;
    import org.flywaydb.core.internal.util.StringUtils;
    import org.flywaydb.core.internal.util.scanner.Scanner;
    import org.springframework.context.ApplicationContext;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.Map;
    
    /**
     * Migration resolver for {@link SpringJdbcMigration}s which are registered in the given {@link ApplicationContext}.
     * This resolver provides the ability to use other beans registered in the {@link ApplicationContext} and reference
     * them via Spring's dependency injection facility inside the {@link SpringJdbcMigration}s.
     */
    public class ApplicationContextAwareSpringJdbcMigrationResolver extends SpringJdbcMigrationResolver {
    
        private final ApplicationContext applicationContext;
    
        public ApplicationContextAwareSpringJdbcMigrationResolver(Scanner scanner, Location location, FlywayConfiguration configuration, ApplicationContext applicationContext) {
            super(scanner, location, configuration);
            this.applicationContext = applicationContext;
        }
    
        @SuppressWarnings("unchecked")
        @Override
        public Collection resolveMigrations() {
            // get all beans of type SpringJdbcMigration from the application context
            Map springJdbcMigrationBeans =
                    (Map) this.applicationContext.getBeansOfType(SpringJdbcMigration.class);
    
            ArrayList resolvedMigrations = new ArrayList();
    
            // resolve the migration and populate it with the migration info
            for (SpringJdbcMigration springJdbcMigrationBean : springJdbcMigrationBeans.values()) {
                ResolvedMigrationImpl resolvedMigration = extractMigrationInfo(springJdbcMigrationBean);
                resolvedMigration.setPhysicalLocation(ClassUtils.getLocationOnDisk(springJdbcMigrationBean.getClass()));
                resolvedMigration.setExecutor(new SpringJdbcMigrationExecutor(springJdbcMigrationBean));
    
                resolvedMigrations.add(resolvedMigration);
            }
    
            Collections.sort(resolvedMigrations, new ResolvedMigrationComparator());
            return resolvedMigrations;
        }
    
        ResolvedMigrationImpl extractMigrationInfo(SpringJdbcMigration springJdbcMigration) {
            Integer checksum = null;
            if (springJdbcMigration instanceof MigrationChecksumProvider) {
                MigrationChecksumProvider version = (MigrationChecksumProvider) springJdbcMigration;
                checksum = version.getChecksum();
            }
    
            String description;
            MigrationVersion version1;
            if (springJdbcMigration instanceof MigrationInfoProvider) {
                MigrationInfoProvider resolvedMigration = (MigrationInfoProvider) springJdbcMigration;
                version1 = resolvedMigration.getVersion();
                description = resolvedMigration.getDescription();
                if (!StringUtils.hasText(description)) {
                    throw new FlywayException("Missing description for migration " + version1);
                }
            } else {
                String resolvedMigration1 = ClassUtils.getShortName(springJdbcMigration.getClass());
                if (!resolvedMigration1.startsWith("V") && !resolvedMigration1.startsWith("R")) {
                    throw new FlywayException("Invalid Jdbc migration class name: " + springJdbcMigration.getClass()
                                                                                                         .getName() + " => ensure it starts with V or R," + " or implement org.flywaydb.core.api.migration.MigrationInfoProvider for non-default naming");
                }
    
                String prefix = resolvedMigration1.substring(0, 1);
                Pair info = MigrationInfoHelper.extractVersionAndDescription(resolvedMigration1, prefix, "__", "");
                version1 = (MigrationVersion) info.getLeft();
                description = (String) info.getRight();
            }
    
            ResolvedMigrationImpl resolvedMigration2 = new ResolvedMigrationImpl();
            resolvedMigration2.setVersion(version1);
            resolvedMigration2.setDescription(description);
            resolvedMigration2.setScript(springJdbcMigration.getClass().getName());
            resolvedMigration2.setChecksum(checksum);
            resolvedMigration2.setType(MigrationType.SPRING_JDBC);
            return resolvedMigration2;
        }
    }
    

    2) Add a new configuration class to post process the Spring Boot generated Flyway instance:

    import org.flywaydb.core.Flyway;
    import org.flywaydb.core.internal.dbsupport.DbSupport;
    import org.flywaydb.core.internal.dbsupport.h2.H2DbSupport;
    import org.flywaydb.core.internal.dbsupport.mysql.MySQLDbSupport;
    import com.pegusapps.zebra.infrastructure.repository.flyway.ApplicationContextAwareSpringJdbcMigrationResolver;
    import org.flywaydb.core.internal.resolver.sql.SqlMigrationResolver;
    import org.flywaydb.core.internal.util.Location;
    import org.flywaydb.core.internal.util.PlaceholderReplacer;
    import org.flywaydb.core.internal.util.scanner.Scanner;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    import javax.sql.DataSource;
    import java.sql.SQLException;
    
    @Configuration
    @ComponentScan("db.migration")
    public class FlywayConfiguration {
    
        @Bean
        public BeanPostProcessor postProcessFlyway(ApplicationContext context) {
            return new BeanPostProcessor() {
    
                @Override
                public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
                    return o;
                }
    
                @Override
                public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
                    if (o instanceof Flyway) {
                        Flyway flyway = (Flyway) o;
                        flyway.setSkipDefaultResolvers(true);
                        ApplicationContextAwareSpringJdbcMigrationResolver resolver = new ApplicationContextAwareSpringJdbcMigrationResolver(
                                new Scanner(Thread.currentThread().getContextClassLoader()),
                                new Location("classpath:db/migration"),
                                context.getBean(org.flywaydb.core.api.configuration.FlywayConfiguration.class),
                                context);
                        SqlMigrationResolver sqlMigrationResolver = null;
                        try {
                            sqlMigrationResolver = new SqlMigrationResolver(
                                    getDbSupport(),
                                    new Scanner(Thread.currentThread().getContextClassLoader()),
                                    new Location("classpath:db/migration"),
                                    PlaceholderReplacer.NO_PLACEHOLDERS,
                                    "UTF-8",
                                    "V",
                                    "R",
                                    "__",
                                    ".sql");
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                        flyway.setResolvers(sqlMigrationResolver, resolver);
                    }
                    return o;
                }
    
                private DbSupport getDbSupport() throws SQLException {
                    DataSource dataSource = context.getBean(DataSource.class);
                    if( ((org.apache.tomcat.jdbc.pool.DataSource)dataSource).getDriverClassName().equals("org.h2.Driver"))
                    {
                        return new H2DbSupport(dataSource.getConnection());
                    }
                    else
                    {
                        return new MySQLDbSupport(dataSource.getConnection());
                    }
                }
            };
        }
    }
    

    Note that I have some hardcoded dependencies on tomcat jdbc pool, h2 and mysql. If you are using something else, you will need to change the code there (If there is anybody that knows how to avoid it, please comment!)

    Also note that the @ComponentScan package needs to match with where you will put the Java migration classes.

    Also note that I had to add the SqlMigrationResolver back in since I want to support both the SQL and the Java flavor of the migrations.

    3) Create a Java class in the db.migrations package that does the actual migration:

    @Component
    public class V2__add_default_surveys implements SpringJdbcMigration {
    
        private final SurveyRepository surveyRepository;
    
        @Autowired
        public V2__add_surveys(SurveyRepository surveyRepository) {
            this.surveyRepository = surveyRepository;
        }
    
        @Override
        public void migrate(JdbcTemplate jdbcTemplate) throws Exception {
            surveyRepository.save(...);
        }
    }
    

    Note that you need to make the class a @Component and it needs to implement the SpringJdbcMigration. In this class, you can use Spring constructor injection for any Spring bean from your context you might need to do the migration(s).

    Note: Be sure to disable ddl validation of Hibernate, because the validation seems to run before Flyway runs:

    spring.jpa.hibernate.ddl-auto=none
    

提交回复
热议问题