Spring Boot Integration¶
What you'll learn
How the Spring Boot starter works under the hood, how to configure it via application.yml, and how to integrate with existing datasource-proxy setups.
QueryAudit provides a Spring Boot starter that automatically intercepts every SQL query executed during your @SpringBootTest tests and analyzes them for performance anti-patterns.
How Auto-Configuration Works¶
When you add query-audit-spring-boot-starter to your test dependencies, three things happen at application startup:
QueryAuditAutoConfigurationis registered viaMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x style).- A shared
QueryInterceptorbean is created. This interceptor records every SQL statement that passes through the proxied DataSource. - A
BeanPostProcessorwraps everyDataSourcebean in a datasource-proxy proxy that feeds query events to the interceptor.
The auto-configuration activates only when:
- A
javax.sql.DataSourceclass is on the classpath (@ConditionalOnClass). - The property
query-audit.enabledistrue(which is the default).
Because the proxy is applied through a BeanPostProcessor, it works transparently with any connection pool (HikariCP, Tomcat, etc.) and any JPA provider (Hibernate, EclipseLink).
Dependency Setup¶
<dependency>
<groupId>io.github.haroya01</groupId>
<artifactId>query-audit-spring-boot-starter</artifactId>
<version>0.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.haroya01</groupId>
<artifactId>query-audit-mysql</artifactId>
<version>0.1.0</version>
<scope>test</scope>
</dependency>
Using PostgreSQL?
Replace query-audit-mysql with query-audit-postgresql in all examples on this page.
application.yml Configuration¶
All properties live under the query-audit prefix. Below is a complete example with every available option and its default value:
query-audit:
enabled: true # Master switch (default: true)
fail-on-detection: true # Fail tests on confirmed issues (default: true)
n-plus-one:
threshold: 3 # Repeated query count before flagging (default: 3)
offset-pagination:
threshold: 1000 # OFFSET value that triggers a warning (default: 1000)
or-clause:
threshold: 3 # OR conditions before flagging (default: 3)
suppress-patterns: # Issue codes or qualified patterns to suppress
- "select-all"
- "missing-where-index:users.email"
report:
format: console # Report format: console (default: console)
output-dir: build/reports/query-audit
show-info: true # Include INFO-level issues in the report (default: true)
Common Configurations¶
Test-only profile
Put this configuration in src/test/resources/application-test.yml and activate it with @ActiveProfiles("test") so it never leaks into production.
Basic Usage with @QueryAudit¶
Annotate your test class (or individual methods) with @QueryAudit. The extension intercepts SQL, runs analysis after each test, and prints a report to the console.
@SpringBootTest
@QueryAudit
class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
void findRecentOrders_shouldUseIndex() {
orderService.findRecentOrders(userId);
// All queries executed by findRecentOrders are captured and analyzed.
// The test fails if a confirmed issue (ERROR or WARNING) is detected.
}
@QueryAudit(failOnDetection = false) // Override: report only, never fail
@Test
void batchExport_reportOnly() {
orderService.exportAll();
}
}
Using with an Existing datasource-proxy (gavlyukovskiy)¶
If your project already uses spring-boot-data-source-decorator (gavlyukovskiy's library), the DataSource is already a datasource-proxy instance. Adding QueryAudit's BeanPostProcessor on top would create a double proxy, which works but adds unnecessary overhead.
There are two approaches to avoid this:
Approach 1: Hook into the existing proxy via a listener (recommended)¶
Disable QueryAudit's own BeanPostProcessor and instead register the QueryInterceptor as an additional listener on the existing proxy.
@TestConfiguration
class QueryAuditTestConfig {
@Bean
public QueryInterceptor queryGuardInterceptor() {
return new QueryInterceptor();
}
@Bean
public BeanPostProcessor attachQueryAuditListener(QueryInterceptor interceptor) {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ProxyDataSource proxyDs) {
// Add QueryInterceptor to the existing proxy's listener chain
proxyDs.getProxyConfig()
.getMethodListener()
.addListener(interceptor);
}
return bean;
}
};
}
}
Then disable QueryAudit's built-in proxy in your test configuration:
# application-test.yml
query-audit:
enabled: false # Disable the auto BeanPostProcessor
fail-on-detection: true # Still used by @QueryAudit annotation
Note
With this approach, QueryAudit's auto-configuration BeanPostProcessor is disabled. The @QueryAudit annotation and JUnit extension still work normally because they read the QueryInterceptor bean from the application context.
Approach 2: Accept the double proxy¶
If simplicity is more important than a few microseconds of overhead per query, just leave both libraries enabled. The proxies stack transparently and both will receive query events. This is perfectly fine for test environments.
DataSource Proxy Wrapping Internals¶
Under the hood, DataSourceProxyFactory.wrap() uses ProxyDataSourceBuilder from datasource-proxy:
The QueryInterceptor implements datasource-proxy's listener interface. When a query executes, the interceptor records the SQL text, parameter bindings, execution time, and a normalized form of the query (for N+1 pattern detection).
Disabling QueryAudit¶
To temporarily disable QueryAudit without removing the dependency:
Or per-test:
Next Steps¶
- Configuration Reference -- Full list of
application.ymlproperties - Annotations Guide -- All 4 annotations explained
- Detection Rules -- All 57 detection rules