【问题标题】:How to configure springboot to wrap DataSource during integration tests?如何在集成测试期间配置 Spring Boot 以包装 DataSource?
【发布时间】:2017-05-04 22:36:18
【问题描述】:

我的目标是进行集成测试,以确保在查找期间不会发生太多数据库查询。 (这有助于我们捕获由于不正确的 JPA 配置而导致的 n+1 个查询)

我知道数据库连接是正确的,因为只要MyDataSourceWrapperConfiguration 不包含在测试中,测试运行期间就没有配置问题。但是,一旦添加,就会发生循环依赖。 (见下面的错误)我相信 @Primary 是必要的,以便 JPA/JDBC 代码使用正确的 DataSource 实例。

MyDataSourceWrapper 是一个自定义类,用于跟踪给定事务发生的查询数量,但它将真正的数据库工作委托给通过构造函数传入的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

【问题讨论】:

  • 您可能对vladmihalcea.com/2014/02/01/… 感兴趣(它实际上使用了一个库来这样做)。要包装您的数据源,我建议使用 BeanPostProcessor 将原始 DataSource 包装在代理类中。

标签: spring spring-boot spring-jdbc


【解决方案1】:

在您的情况下,您将获得 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-proxydatasource-assert 感兴趣,而不是滚动您自己的包装datasource-assert,后者已经支持计数器等(节省您维护自己的组件)。

【讨论】:

  • 就是这样。 BeanPostProcessor 是关键。感谢datasource-assert 的推荐,我正在使用它,但为了示例的简单性,将其省略了。但是,在这里为其他人提及它是一件好事。谢谢! (小注,你的例子应该是instanceof
  • 已修复...(这实际上是自动更正的结果:))。
  • 这是在 spring-boot 2.0.0.M3 中中断的,它曾经工作到 2.0.0.M2
  • 哇!谢谢你把我介绍给BeanPostProcessor - 这家伙是个传奇:)
【解决方案2】:

从 spring boot 2.0.0.M3 开始使用 BeanPostProcessor 将无法正常工作。

作为一种解决方法,创建您自己的 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 的datasource-proxy 版本

【讨论】:

  • 只是指出,一旦我们升级到 springboot 2.x,这个解决方案就奏效了。
【解决方案3】:

你实际上仍然可以在 Spring Boot 2 中使用 BeanPostProcessor,但它需要返回正确的类型(声明的 Bean 的实际类型)。为此,您需要创建一个正确类型的代理,它将 DataSource 方法重定向到您的拦截器,并将所有其他方法重定向到原始 bean。

有关示例代码,请参阅 https://github.com/spring-projects/spring-boot/issues/12592 的 Spring Boot 问题和讨论。

【讨论】:

  • 拯救了我的一天 :)
  • 有关信息:使用 Spring Boot 2.5 我可以从我的 DataSource 返回不同的 DataSource 类型BeanPostProcessor 所以我不必求助于代理。很可能您遇到的限制仅在 Spring Boot 2.0 的早期是真实的?
【解决方案4】:

以下解决方案适用于我使用 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);
    }

}

【讨论】:

    猜你喜欢
    • 2021-02-16
    • 2017-10-29
    • 2015-12-24
    • 2021-01-24
    • 2019-12-23
    • 2019-12-19
    • 2016-10-10
    • 2019-02-25
    • 2018-11-09
    相关资源
    最近更新 更多