【问题标题】:Spring beans are not injected in flyway java based migrationSpring bean 未注入基于 flyway java 的迁移
【发布时间】:2016-04-27 16:55:35
【问题描述】:

我正在尝试在 flyway 迁移 java 代码中注入配置属性的组件,但它始终为空。

我正在使用带有 Flyway 的弹簧靴。

@Component
@ConfigurationProperties(prefix = "code")
public class CodesProp {

    private String codePath;
 }

然后在 Flyway 迁移代码中,尝试自动编写此组件,如下所示:

public class V1_4__Migrate_codes_metadata implements SpringJdbcMigration {

@Autowired
private CodesProp codesProp ;
public void migrate(JdbcTemplate jdbcTemplate) throws Exception {
    codesProp.getCodePath();  
}

这里,codeProp 始终为空。

有没有办法在flyway里面注入spring bean或者让它在flyway bean之前初始化?

谢谢。

【问题讨论】:

    标签: spring spring-boot flyway


    【解决方案1】:

    Flyway 不支持对 SpringJdbcMigration 实现的依赖注入。它只是在类路径上查找实现SpringJdbcMigration 的类,并使用默认构造函数创建一个新实例。这是在SpringJdbcMigrationResolver 中执行的。执行迁移时,SpringJdbcMigrationExecutor 会创建一个新的JdbcTemplate,然后调用您的迁移实现的migrate 方法。

    如果您确实需要将依赖项注入到基于 Java 的迁移中,我认为您必须实现自己的 MigrationResolver,它从应用程序上下文中检索特定类型的 bean 并创建并返回 ResolvedMigration每个实例。

    【讨论】:

    • 谢谢,我认为它与问题github.com/flyway/flyway/issues/1062 相关,现在,我需要将一些存储在文件系统中的代码导入数据库。您是否有想法将外部路径作为配置读取并将其传递给 flyway。
    【解决方案2】:

    如果和我一样,不想等待 Flyway 4.1,可以使用 Flyway 4.0 并将以下内容添加到您的 Spring Boot 应用程序中:

    1) 在你的项目中创建一个ApplicationContextAwareSpringJdbcMigrationResolver 类:

    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<ResolvedMigration> resolveMigrations() {
            // get all beans of type SpringJdbcMigration from the application context
            Map<String, SpringJdbcMigration> springJdbcMigrationBeans =
                    (Map<String, SpringJdbcMigration>) this.applicationContext.getBeansOfType(SpringJdbcMigration.class);
    
            ArrayList<ResolvedMigration> resolvedMigrations = new ArrayList<ResolvedMigration>();
    
            // 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) 添加一个新的配置类来后期处理 Spring Boot 生成的 Flyway 实例:

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

    请注意,我对 tomcat jdbc pool、h2 和 mysql 有一些硬编码的依赖项。如果您正在使用其他东西,则需要更改那里的代码(如果有人知道如何避免它,请发表评论!)

    另请注意,@ComponentScan 包需要与您放置 Java 迁移类的位置相匹配。

    另外请注意,我必须重新添加 SqlMigrationResolver,因为我想同时支持 SQL 和 Java 风格的迁移。

    3) 在执行实际迁移的 db.migrations 包中创建一个 Java 类:

    @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(...);
        }
    }
    

    请注意,您需要将类设为@Component,并且它需要实现SpringJdbcMigration。在此类中,您可以使用 Spring 构造函数注入从您可能需要执行迁移的上下文中的任何 Spring bean。

    注意:一定要禁用 Hibernate 的 ddl 验证,因为验证似乎在 Flyway 运行之前运行:

    spring.jpa.hibernate.ddl-auto=none
    
    【解决方案3】:

    如果您使用 deltaspike,您可以使用 BeanProvider 来获取对您的类的引用。这是一个 DAO 示例,但它也应该适用于您的类。

    更改您的 DAO 代码:

    public static UserDao getInstance() {
        return BeanProvider.getContextualReference(UserDao.class, false, new DaoLiteral());
    }
    

    然后在你的迁移方法中:

    UserDao userdao = UserDao.getInstance();
    

    你已经得到了你的参考。

    (引用自:Flyway Migration with java

    【讨论】:

      【解决方案4】:

      简而言之,不要在您的数据库迁移中自动装配 bean,甚至不要从您的应用程序中引用类! 如果您重构/删除/更改您在迁移中引用的类,它甚至可能无法编译或更糟地破坏您的迁移。

      使用纯 JDBC 模板进行迁移的开销不值得冒险。

      【讨论】:

        猜你喜欢
        • 2016-02-05
        • 1970-01-01
        • 1970-01-01
        • 2017-02-07
        • 2017-12-23
        • 2019-08-07
        • 2015-12-19
        • 2015-01-02
        • 2021-02-07
        相关资源
        最近更新 更多