如果某些bean仅在测试模式下存在,如何以正确的顺序初始化bean?

时间:2016-02-06 13:21:02

标签: java spring jpa spring-boot spring-bean

美好的一天。我的Spring Boot应用程序使用Postgress数据库。对于测试,它使用H2数据库。在非测试模式下运行时,需要按以下顺序初始化bean:

1) Init DataSource
2) Init JPA beans

在测试模式下运行时,我需要在JPA bean初始化之前创建并填充H2数据库:

1) Init DataSource
2) Init DataSourceInitializer
3) Init JPA beans

问题是JPA bean在DataSourceInitializer之前被初始化(步骤3在步骤2之前)并且在缺失表上测试失败(hibernate.hbm2ddl.auto = validate)。

第1步

@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "datasource.runtime")
    public DataSource runtimeDataSource() {
        return DataSourceBuilder.create().build();
    }
}

第2步

@Configuration
@Profile(Profiles.INTEGRATION_TEST)
public class DataSourceTestConfig {

    @Autowired
    private ResourceLoader resourceLoader;

    @Bean
    public DataSourceInitializer runtimeDataSourceInitializer(@Qualifier("runtimeDataSource") DataSource dataSource) {
        DataSourceInitializer initializer = new DataSourceInitializer();
        initializer.setDataSource(dataSource);
        initializer.setDatabasePopulator(new ResourceDatabasePopulator(
                resourceLoader.getResource("classpath:runtime/schema.sql")
        ));
        return initializer;
    }
}

第3步

@Configuration
@EnableTransactionManagement
public class JpaConfig {

    @Autowired
    private Environment environment;

    @Autowired
    @Qualifier(value = "runtimeDataSource")
    private DataSource runtimeDataSource;

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean runtimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(runtimeDataSource)
                .properties(hibernateSettings())
                .packages(
                        "cz.adx.anx.car.cases.domain",
                        "cz.adx.anx.car.lib.domain",
                        "org.springframework.data.jpa.convert.threeten" // Hibernate support for Java 8 date and time classes
                )
                .persistenceUnit("runtimePersistenceUnit")
                .build();
    }
}

我需要来自类DataSourceTestConfig的bean在JpaConfig之前和DataSourceConfig之后初始化,但仅在测试模式下。在非测试模式下,应该在DataSourceConfig之后初始化来自JpaConfig的bean,并且必须省略来自DataSourceTestConfig的bean。因此,我无法使用类DataSourceTestConfig中的@DependsOn bean来注释类JpaConfig,因为此类位于测试包中而不存在于非测试模式中。我可以复制配置类并使它们以配置文件为条件,但我对这个解决方案感到不舒服。拜托,有更好的解决方案吗?提前谢谢!

PS:我的应用程序使用两个数据库/数据源但我缩短了上面的代码以便于阅读。我使用的是Spring Boot 1.3.1.RELEASE。

更新1: 我尝试使用@luboskrnac建议的方法。我在我的集​​成测试类上放置了注释ActiveProfiles:

@ActiveProfiles("IT")
public abstract class IntegrationTest {...}

我在JpaConfig类中的相关bean上使用了注释Profile,如下所示:

@Configuration
@EnableTransactionManagement
public class JpaConfig {

    @Autowired
    private Environment environment;

    @Autowired
    @Qualifier(value = "runtimeDataSource")
    private DataSource runtimeDataSource;

    @Autowired
    @Qualifier(value = "configDataSource")
    private DataSource configDataSource;

    @Profile("!IT")
    @Bean(name = "runtimeEntityManagerFactory")
    @DependsOn("runtimeDataSource")
    public LocalContainerEntityManagerFactoryBean runtimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return createRuntimeEntityManagerFactory(builder);
    }

    @Profile("IT")
    @Bean(name = "runtimeEntityManagerFactory")
    @DependsOn("runtimeDataSourceInitializer")
    public LocalContainerEntityManagerFactoryBean testRuntimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return jpaConfig.createRuntimeEntityManagerFactory(builder);
    }

    public LocalContainerEntityManagerFactoryBean createRuntimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder
            .dataSource(runtimeDataSource)
            .properties(hibernateSettings())
            .packages(
                "cz.adx.anx.car.cases.domain",
                "cz.adx.anx.car.lib.domain",
                "org.springframework.data.jpa.convert.threeten" // Hibernate support for Java 8 date and time classes
            )
            .persistenceUnit("runtimePersistenceUnit")
            .build();
    }
}

我以同样的方式创建了事务管理器。因为我使用两个数据源(两个不同的数据库),所以我在EnableJpaRepositories注释中使用了bean名称。

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "runtimeEntityManagerFactory",
        transactionManagerRef = "runtimeTransactionManager",
        basePackages = "cz.adx.anx.car.lib.repository"
)
public class JpaCarLibRepositoryConfig {
}

所以我需要以相同名称注册的非测试bean和测试bean。但是Spring给了我一个例外:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: runtimeEntityManagerFactory

有什么建议吗?

1 个答案:

答案 0 :(得分:1)

我建议删除有关显式bean创建排序或bean依赖项的任何注意事项。

只需根据Spring @Sql annotation在测试中填充数据库。测试看起来像这样:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Sql("/test-schema.sql")
public class DatabaseTests {

    @Test
    public void emptySchemaTest {
        // execute code that uses the test schema without any test data
    }

    @Test
    @Sql({"/test-schema.sql", "/test-user-data.sql"})
    public void userTest {
        // execute code that uses the test schema and test data
    }
}

如果你需要交换数据源(例如在测试中使用PROD中的PostgereSQL和测试中的H2),只需使用Spring @Profile@ActiveProfiles注释。