Queries are counted twice when a DataSource bean delegates to another DataSource bean

Open
#263 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
48/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Domain
backend, testing

Research direction

Start with QuickPerfProxyBeanPostProcessor and ProxyDataSourceInterceptor, then reproduce the issue with the DoubleWrapTest setup and QuickPerfSqlConfig. Done means a delegated DataSource records one query once while independent DataSource beans remain recordable, with the regression covered by tests.

Written by the indexing model from the issue text.

Description

:bug: bug

Describe the bug

When two DataSource beans exist and one delegates to the other, a single query is counted twice. LazyConnectionDataSourceProxy is a common way to end up in that setup.

QuickPerfProxyBeanPostProcessor wraps any bean of type DataSource and has no guard against one whose queries are already recorded:

if (bean instanceof DataSource && !ScopedProxyUtils.isScopedTarget(beanName)) {
    final ProxyFactory factory = new ProxyFactory(bean);
    factory.setProxyTargetClass(true);
    factory.addAdvice(new ProxyDataSourceInterceptor((DataSource) bean));
    return factory.getProxy();
}

The inner DataSource bean is created and wrapped first. The outer bean then receives the already wrapped inner one as its delegate, and gets wrapped as well because it is a DataSource too. The query is recorded once by the outer proxy and once by the inner one.

Expected behavior

One select statement is counted once and @ExpectSelect(1) passes.

Actual behavior

It is counted twice:

java.lang.AssertionError: a performance-related property is not respected

[PERF] You may think that <1> select statement was sent to the database
       But there are in fact <2>...

[JDBC QUERY EXECUTION (executeQuery, executeBatch, ...)]
	Time:3, Success:True, Type:Statement, Batch:False, QuerySize:1, BatchSize:0, Query:["select count(*) from member"], Params:[]

	Time:4, Success:True, Type:Statement, Batch:False, QuerySize:1, BatchSize:0, Query:["select count(*) from member"], Params:[]

To Reproduce

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class App {

    @Bean
    public DataSource realDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .generateUniqueName(true)
            .build();
    }

    @Bean
    @Primary
    public DataSource lazyDataSource(DataSource realDataSource) {
        return new LazyConnectionDataSourceProxy(realDataSource);
    }

}
@SpringBootTest(classes = App.class)
@Import(QuickPerfSqlConfig.class)
@QuickPerfTest
class DoubleWrapTest {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @BeforeEach
    void initSchema() {
        jdbcTemplate.execute("DROP TABLE IF EXISTS member");
        jdbcTemplate.execute("CREATE TABLE member (id INT PRIMARY KEY, name VARCHAR(100))");
    }

    @Test
    @ExpectSelect(1)
    void oneSelectShouldBeCountedOnce() {
        jdbcTemplate.queryForObject("select count(*) from member", Integer.class);
    }

}

The same test passes with one recorded statement on an application that declares a single DataSource bean.

Versions

  • QuickPerf: reproduced on both 1.1.0 and master (3b96d125dacf06b74849cca62df9d60871a86464)
  • JDK: 17.0.20 (Temurin)
  • OS: macOS
  • Database: H2
  • Spring Boot: 2.7.18 and 3.2.5 (same result on both)

Additional context

The count silently doubles, and the failure message gives no hint about the DataSource setup.

I have this fixed on another datasource-proxy based project: attach a marker interface to the proxy when wrapping, then walk the delegation chain before wrapping and skip a DataSource that already carries the marker. Two things need care. AbstractRoutingDataSource is not a DelegatingDataSource, and a skip condition that is too broad silently drops a second, independent DataSource.

Happy to open a pull request with tests if this direction works for you.

Dominant language
Java
Stars
541
Forks
73
PR merge metrics
No merged PRs in 30d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from quick-perf/quickperf

All issues in quick-perf/quickperf

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.