【问题标题】:Flyway Spring Boot Autowired Beans with JPA Dependency具有 JPA 依赖关系的 Flyway Spring Boot Autowired Bean
【发布时间】:2018-06-21 05:43:21
【问题描述】:

我正在使用 Flyway 5.0.5,但我无法创建具有自动装配属性的 java (SpringJdbcMigration)...它们最终是 null

我能找到的最接近的是这个问题:Spring beans are not injected in flyway java based migration

答案提到它已在 Flyway 5 中修复,但链接已失效。

我错过了什么?

【问题讨论】:

    标签: jpa spring-boot java-8 flyway


    【解决方案1】:

    由于我对 JPA 的依赖,我为此苦苦挣扎了很长时间。我将稍微编辑我的问题的标题以反映这一点......

    @Autowired bean 是从 ApplicationContext 实例化的。我们可以创建一个不同的 bean ApplicationContextAware 并使用它来“手动连接”我们的 bean 以用于迁移。

    可以找到一个非常干净的方法here。不幸的是,这在使用 JPA 时会引发未捕获的异常(具体而言,ApplicationContext 为空)。幸运的是,我们可以通过使用@DependsOn 注解并在设置ApplicationContext 后强制flyway 运行来解决这个问题。

    首先我们需要来自上述avehlies/spring-beans-flyway2SpringUtility

    package com.mypackage;
    
    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;
        }
    
    }
    

    然后,为springUtility 配置flywayInitializer@DependsOn。我在这里扩展了FlywayAutoConfiguration,希望保留自动配置功能。这似乎对我有用,除了在我的 gradle.build 文件中关闭 flyway 不再有效,所以我不得不添加 @Profile("!integration") 以防止它在我的测试期间运行。除此之外,自动配置似乎对我有用,但我承认我只运行了一次迁移。如果我错了,希望有人能纠正我。

    package com.mypackage;
    
    import org.flywaydb.core.Flyway;
    import org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializer;
    import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayConfiguration;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    import org.springframework.context.annotation.Primary;
    import org.springframework.context.annotation.DependsOn;
    
    import com.mypackage.SpringUtility;
    
    @Configuration
    @Profile("!integration")
    class MyFlywayConfiguration extends FlywayConfiguration {
        @Primary
        @Bean(name = "flywayInitializer")
        @DependsOn("springUtility")
        public FlywayMigrationInitializer flywayInitializer(Flyway flyway){
            return super.flywayInitializer(flyway);
            //return new FlywayMigrationInitializer(flyway, null);
        }
    }
    

    为了完成示例,这里是一个迁移:

    package db.migration;
    
    import org.flywaydb.core.api.migration.spring.BaseSpringJdbcMigration;
    import org.springframework.jdbc.core.JdbcTemplate;
    
    import org.springframework.stereotype.Component;
    
    import com.mypackage.repository.AccountRepository;
    import com.mypackage.domain.Account;
    
    import com.mypackage.SpringUtility;
    
    import java.util.List;
    
    public class V2__account_name_ucase_firstname extends BaseSpringJdbcMigration {
    
        private AccountRepository accountRepository = SpringUtility.getBean(AccountRepository.class);
    
        public void migrate(JdbcTemplate jdbcTemplate) throws Exception {
    
            List<Account> accounts = accountRepository.findAll();
    
            for (Account account : accounts) {
    
                String firstName = account.getFirstName();
                account.setFirstName(firstName.substring(0, 1).toUpperCase() + firstName.substring(1));
                account = accountRepository.save(account);
    
            }
        }
    }
    

    感谢 github 上的 avehlies、堆栈溢出的 Andy Wilkinson 和 github 上的 OldIMP 一路帮助我。

    如果您使用的是更新版本的 Flyway,请扩展 BaseJavaMigration 而不是 BaseSpringJdbcMigration,因为后者已弃用。另外,看看下面的twocomments用户Wim Deblauwe。

    【讨论】:

    • 使用getBean(final Class&lt;T&gt; clazz) 避免方法体中的强制转换
    • 用 Spring Boot 1.5.14 测试,我需要一个构造函数到 MyFlywayConfigurationFlywayConfiguration 类匹配。否则工作正常!
    • 这太危险了……你怎么知道以后永远不会删除或更改帐户?当它这样做时,你会操纵历史脚本吗?无法保证您的软件仍然可以使用干净的数据库从头开始安装...
    • 这个答案很棒。但是,我相信自从 Flyway 6 以来,这变得更容易了? flywaydb.org/documentation/api/…
    • 静态字段不支持Autowired注解,如上面SpringUtility中的注解。
    【解决方案2】:

    该功能尚未进入 Flyway。 this issue 正在跟踪它。在撰写本文时,该问题已打开并已分配给 5.1.0 里程碑。

    【讨论】:

      【解决方案3】:

      似乎@mararn1618 提供的更新答案未在官方文档中记录,因此我将在此处提供工作设置。感谢@mararn1618 指导这个方向。

      免责声明,它是用 Kotlin 编写的 :)

      首先,您需要一个用于加载迁移类的配置,在 Spring Boot(可能还有 Spring)中,您需要实现 FlywayConfigurationCustomizer 或设置 FlywayAutoConfiguration.FlywayConfiguration。只测试了第一个,但两者都应该工作

      配置 a,经过测试

      import org.flywaydb.core.api.configuration.FluentConfiguration
      import org.flywaydb.core.api.migration.JavaMigration
      import org.springframework.beans.factory.annotation.Autowired
      import org.springframework.boot.autoconfigure.flyway.FlywayConfigurationCustomizer
      import org.springframework.context.ApplicationContext
      import org.springframework.stereotype.Component
      
      @Component
      class MyFlywayConfiguration @Autowired constructor(
              val applicationContext: ApplicationContext
      ) : FlywayConfigurationCustomizer {
          override fun customize(configuration: FluentConfiguration?) {
              val migrationBeans = applicationContext.getBeansOfType(JavaMigration::class.java)
              val migrationBeansAsArray = migrationBeans.values.toTypedArray()
              configuration?.javaMigrations(*migrationBeansAsArray)
          }
      }
      

      配置选项 B,未经测试,但也应该可以工作

      import org.flywaydb.core.api.migration.JavaMigration
      import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
      import org.springframework.boot.autoconfigure.flyway.FlywayConfigurationCustomizer
      import org.springframework.context.ApplicationContext
      import org.springframework.context.annotation.Bean
      import org.springframework.context.annotation.Configuration
      
      @Configuration
      class MyFlywayConfiguration : FlywayAutoConfiguration.FlywayConfiguration() {
          @Bean
          fun flywayConfigurationCustomizer(applicationContext: ApplicationContext): FlywayConfigurationCustomizer {
              return FlywayConfigurationCustomizer { flyway ->
                  val p = applicationContext.getBeansOfType(JavaMigration::class.java)
                  val v = p.values.toTypedArray()
      
                  flyway.javaMigrations(*v)
              }
          }
      }
      

      这样你就可以像几乎任何其他 Spring bean 一样编写迁移:

      import org.flywaydb.core.api.migration.BaseJavaMigration
      import org.flywaydb.core.api.migration.Context
      import org.springframework.beans.factory.annotation.Autowired
      import org.springframework.stereotype.Component
      
      @Component
      class V7_1__MyMigration @Autowired constructor(
      ) : BaseJavaMigration() {
          override fun migrate(context: Context?) {
              TODO("go crazy, mate, now you can import beans, but be aware of circular dependencies")
          }
      }
      

      旁注:

      • 注意循环依赖,您的迁移很可能不依赖于存储库(这也是有道理的,毕竟您正在准备它们)
      • 确保您的迁移位于 Spring 扫描类的位置。所以如果你想把它们放在命名空间db/migrations中,你需要确保Spring会扫描那个位置
      • 我没有测试过,但可能应该谨慎混合这些迁移的路径和 Flyway 扫描迁移的位置

      【讨论】:

      • 使用选项 B,我遇到了初始化顺序的问题:它会先尝试初始化数据库,这会导致迁移无法运行。选项 A 工作正常。
      【解决方案4】:

      当前 flyway 6.5.5 版本已发布并从 6.0.0 返回我相信提供了对 spring beans 的支持。 您可以直接将 spring bean 自动装配到基于 Java 的迁移中(使用 @autowired),但预感是您的 Migration 类也应该由 Spring 管理以解决依赖关系。 有一种很酷且简单的方法,通过覆盖 Flyway 的默认行为,查看https://reflectoring.io/database-migration-spring-boot-flyway/ 这篇文章用代码 sn-ps 清楚地回答了你的问题。

      【讨论】:

      • 你能详细说明一下吗?我不确定我必须做什么,但我使用的是 v6.5.5 并且 @Autowired 无法正常工作。
      【解决方案5】:

      如果您使用的是 deltaspike,您可以使用 BeanProvider 来获取对您的 DAO 的引用。

      更改您的 DAO 代码:

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

      然后在你的迁移方法中:

      UserDao userdao = UserDao.getInstance();
      

      你已经得到了你的参考。

      (引用自:Flyway Migration with java

      【讨论】:

        猜你喜欢
        • 2019-06-24
        • 1970-01-01
        • 2016-06-21
        • 2019-04-26
        • 1970-01-01
        • 2018-07-26
        • 1970-01-01
        • 2018-07-02
        • 1970-01-01
        相关资源
        最近更新 更多