如果某些 bean 仅在测试模式下存在,如何以正确的顺序初始化 bean?
How to initialize beans in proper order if some beans are present in test mode only?
美好的一天。我的 Spring 启动应用程序使用 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 步之前)并且缺少表时测试失败(休眠。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();
}
}
我需要来自 class DataSourceTestConfig 的 bean 在 JpaConfig 之前和 DataSourceConfig 之后初始化,但仅限于测试模式。在非测试模式下,来自 JpaConfig 的 bean 应该在 DataSourceConfig 之后初始化,并且必须省略来自 DataSourceTestConfig 的 bean。因此,我无法使用来自 class DataSourceTestConfig 的 @DependsOn bean 注释 class JpaConfig,因为此 class 位于测试包中,而不存在于非测试模式中。我可以复制配置 classes 并使它们以配置文件为条件,但我对这个解决方案感到不舒服。请问,有更好的解决办法吗?提前致谢!
PS:我的应用程序使用两个 databases/datasources,但我缩短了上面的代码以使其更易于阅读。我正在使用 Spring Boot 1.3.1.RELEASE.
更新 1:
我尝试使用@luboskrnac 建议的方法。我在我的集成测试中放置了注释 ActiveProfiles classes:
@ActiveProfiles("IT")
public abstract class IntegrationTest {...}
并且我在 class 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
有什么建议吗?
我建议放弃任何关于显式 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
注释。
美好的一天。我的 Spring 启动应用程序使用 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 步之前)并且缺少表时测试失败(休眠。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();
}
}
我需要来自 class DataSourceTestConfig 的 bean 在 JpaConfig 之前和 DataSourceConfig 之后初始化,但仅限于测试模式。在非测试模式下,来自 JpaConfig 的 bean 应该在 DataSourceConfig 之后初始化,并且必须省略来自 DataSourceTestConfig 的 bean。因此,我无法使用来自 class DataSourceTestConfig 的 @DependsOn bean 注释 class JpaConfig,因为此 class 位于测试包中,而不存在于非测试模式中。我可以复制配置 classes 并使它们以配置文件为条件,但我对这个解决方案感到不舒服。请问,有更好的解决办法吗?提前致谢!
PS:我的应用程序使用两个 databases/datasources,但我缩短了上面的代码以使其更易于阅读。我正在使用 Spring Boot 1.3.1.RELEASE.
更新 1: 我尝试使用@luboskrnac 建议的方法。我在我的集成测试中放置了注释 ActiveProfiles classes:
@ActiveProfiles("IT")
public abstract class IntegrationTest {...}
并且我在 class 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
有什么建议吗?
我建议放弃任何关于显式 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
注释。