如何在集成测试期间配置 spring boot 来包装 DataSource?
How to configure springboot to wrap DataSource during integration tests?
我的目标是进行集成测试,以确保在查找期间不会发生太多数据库查询。 (这有助于我们捕获由于不正确的 JPA 配置而导致的 n+1 个查询)
我知道数据库连接是正确的,因为在测试期间没有配置问题 运行 只要 MyDataSourceWrapperConfiguration
不包括在测试中。但是,一旦添加,就会发生循环依赖。 (请参阅下面的错误)我认为 @Primary
是 JPA/JDBC 代码使用正确的 DataSource
实例所必需的。
MyDataSourceWrapper
是一个自定义的 class,它跟踪给定事务发生的查询数量,但它将真正的数据库工作委托给通过构造函数传入的 DataSource
.
错误:
The dependencies of some of the beans in the application context form a cycle:
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
┌─────┐
| databaseQueryCounterProxyDataSource defined in me.testsupport.database.MyDataSourceWrapperConfiguration
↑ ↓
| dataSource defined in org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Tomcat
↑ ↓
| dataSourceInitializer
└─────┘
我的配置:
@Configuration
public class MyDataSourceWrapperConfiguration {
@Primary
@Bean
DataSource databaseQueryCounterProxyDataSource(final DataSource delegate) {
return MyDataSourceWrapper(delegate);
}
}
我的测试:
@ActiveProfiles({ "it" })
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration({ DatabaseConnectionConfiguration.class, DatabaseQueryCounterConfiguration.class })
@EnableAutoConfiguration
public class EngApplicationRepositoryIT {
@Rule
public MyDatabaseQueryCounter databaseQueryCounter = new MyDatabaseQueryCounter ();
@Rule
public ErrorCollector errorCollector = new ErrorCollector();
@Autowired
MyRepository repository;
@Test
public void test() {
this.repository.loadData();
this.errorCollector.checkThat(this.databaseQueryCounter.getSelectCounts(), is(lessThan(10)));
}
}
更新:这个最初的问题是针对 springboot 1.5 的。接受的答案反映出,@rajadilipkolli 的答案适用于 springboot 2.x
在您的情况下,您将获得 2 个 DataSource
实例,这可能不是您想要的。而是使用 BeanPostProcessor
这是实际为此设计的组件。另见 Spring Reference Guide.
创建并注册一个执行包装的 BeanPostProcessor
。
public class DataSourceWrapper implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof DataSource) {
return new MyDataSourceWrapper((DataSource)bean);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
然后只需将其注册为 @Bean
而不是您的 MyDataSourceWrapper
。
提示: 你可能对 datasource-proxy combined with datasource-assert 感兴趣,而不是滚动你自己的包装 DataSource
,它已经有计数器等支持(节省你维护你的自己的组件)。
从 spring 开始,使用 BeanPostProcessor 启动 2.0.0.M3 将无法工作。
作为解决方法,创建您自己的 bean,如下所示
@Bean
public DataSource customDataSource(DataSourceProperties properties) {
log.info("Inside Proxy Creation");
final HikariDataSource dataSource = (HikariDataSource) properties
.initializeDataSourceBuilder().type(HikariDataSource.class).build();
if (properties.getName() != null) {
dataSource.setPoolName(properties.getName());
}
return ProxyDataSourceBuilder.create(dataSource).countQuery().name("MyDS")
.logSlowQueryToSysOut(1, TimeUnit.MINUTES).build();
}
另一种方法是使用 datasource-decorator starter
的数据源代理版本
实际上您仍然可以在 Spring Boot 2 中使用 BeanPostProcessor,但它需要 return 正确的类型(声明的 Bean 的实际类型)。为此,您需要创建一个正确类型的代理,它将 DataSource 方法重定向到您的拦截器,并将所有其他方法重定向到原始 bean。
示例代码参见 Spring 引导问题和讨论 https://github.com/spring-projects/spring-boot/issues/12592。
以下解决方案适用于我使用 Spring Boot 2.0.6.
它使用显式绑定而不是注释@ConfigurationProperties(prefix = "spring.datasource.hikari")
。
@Configuration
public class DataSourceConfig {
private final Environment env;
@Autowired
public DataSourceConfig(Environment env) {
this.env = env;
}
@Primary
@Bean
public MyDataSourceWrapper primaryDataSource(DataSourceProperties properties) {
DataSource dataSource = properties.initializeDataSourceBuilder().build();
Binder binder = Binder.get(env);
binder.bind("spring.datasource.hikari", Bindable.ofInstance(dataSource).withExistingValue(dataSource));
return new MyDataSourceWrapper(dataSource);
}
}
我的目标是进行集成测试,以确保在查找期间不会发生太多数据库查询。 (这有助于我们捕获由于不正确的 JPA 配置而导致的 n+1 个查询)
我知道数据库连接是正确的,因为在测试期间没有配置问题 运行 只要 MyDataSourceWrapperConfiguration
不包括在测试中。但是,一旦添加,就会发生循环依赖。 (请参阅下面的错误)我认为 @Primary
是 JPA/JDBC 代码使用正确的 DataSource
实例所必需的。
MyDataSourceWrapper
是一个自定义的 class,它跟踪给定事务发生的查询数量,但它将真正的数据库工作委托给通过构造函数传入的 DataSource
.
错误:
The dependencies of some of the beans in the application context form a cycle:
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
┌─────┐
| databaseQueryCounterProxyDataSource defined in me.testsupport.database.MyDataSourceWrapperConfiguration
↑ ↓
| dataSource defined in org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Tomcat
↑ ↓
| dataSourceInitializer
└─────┘
我的配置:
@Configuration
public class MyDataSourceWrapperConfiguration {
@Primary
@Bean
DataSource databaseQueryCounterProxyDataSource(final DataSource delegate) {
return MyDataSourceWrapper(delegate);
}
}
我的测试:
@ActiveProfiles({ "it" })
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration({ DatabaseConnectionConfiguration.class, DatabaseQueryCounterConfiguration.class })
@EnableAutoConfiguration
public class EngApplicationRepositoryIT {
@Rule
public MyDatabaseQueryCounter databaseQueryCounter = new MyDatabaseQueryCounter ();
@Rule
public ErrorCollector errorCollector = new ErrorCollector();
@Autowired
MyRepository repository;
@Test
public void test() {
this.repository.loadData();
this.errorCollector.checkThat(this.databaseQueryCounter.getSelectCounts(), is(lessThan(10)));
}
}
更新:这个最初的问题是针对 springboot 1.5 的。接受的答案反映出,@rajadilipkolli 的答案适用于 springboot 2.x
在您的情况下,您将获得 2 个 DataSource
实例,这可能不是您想要的。而是使用 BeanPostProcessor
这是实际为此设计的组件。另见 Spring Reference Guide.
创建并注册一个执行包装的 BeanPostProcessor
。
public class DataSourceWrapper implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof DataSource) {
return new MyDataSourceWrapper((DataSource)bean);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
然后只需将其注册为 @Bean
而不是您的 MyDataSourceWrapper
。
提示: 你可能对 datasource-proxy combined with datasource-assert 感兴趣,而不是滚动你自己的包装 DataSource
,它已经有计数器等支持(节省你维护你的自己的组件)。
从 spring 开始,使用 BeanPostProcessor 启动 2.0.0.M3 将无法工作。
作为解决方法,创建您自己的 bean,如下所示
@Bean
public DataSource customDataSource(DataSourceProperties properties) {
log.info("Inside Proxy Creation");
final HikariDataSource dataSource = (HikariDataSource) properties
.initializeDataSourceBuilder().type(HikariDataSource.class).build();
if (properties.getName() != null) {
dataSource.setPoolName(properties.getName());
}
return ProxyDataSourceBuilder.create(dataSource).countQuery().name("MyDS")
.logSlowQueryToSysOut(1, TimeUnit.MINUTES).build();
}
另一种方法是使用 datasource-decorator starter
的数据源代理版本实际上您仍然可以在 Spring Boot 2 中使用 BeanPostProcessor,但它需要 return 正确的类型(声明的 Bean 的实际类型)。为此,您需要创建一个正确类型的代理,它将 DataSource 方法重定向到您的拦截器,并将所有其他方法重定向到原始 bean。
示例代码参见 Spring 引导问题和讨论 https://github.com/spring-projects/spring-boot/issues/12592。
以下解决方案适用于我使用 Spring Boot 2.0.6.
它使用显式绑定而不是注释@ConfigurationProperties(prefix = "spring.datasource.hikari")
。
@Configuration
public class DataSourceConfig {
private final Environment env;
@Autowired
public DataSourceConfig(Environment env) {
this.env = env;
}
@Primary
@Bean
public MyDataSourceWrapper primaryDataSource(DataSourceProperties properties) {
DataSource dataSource = properties.initializeDataSourceBuilder().build();
Binder binder = Binder.get(env);
binder.bind("spring.datasource.hikari", Bindable.ofInstance(dataSource).withExistingValue(dataSource));
return new MyDataSourceWrapper(dataSource);
}
}