Skip to content

QueryAudit

Stop shipping slow queries. Catch N+1, missing indexes, and 57 other SQL anti-patterns automatically during your JUnit tests.

Get Started View on GitHub


57 Detection Rules

Catches N+1 queries, missing indexes, SELECT *, DML anti-patterns, batch insert optimization, functions in WHERE clauses, implicit type conversions, locking risks, ORM inefficiencies, and more.

100% Reliable

Confirmed detections are structural -- they inspect SQL text and index metadata, not runtime data. No flaky heuristics. No false positives. If QueryAudit flags it, it is a real problem.

Zero Config

Add one annotation to your test class. That's it. QueryAudit auto-discovers your DataSource, intercepts queries, analyzes them, and reports issues. Works with JUnit 5 and Spring Boot. Supports MySQL and PostgreSQL.

Actionable Reports

Every issue includes the exact SQL statement, affected table, column, the detection rule that fired, and a concrete fix suggestion you can apply immediately.


How It Works

:material-test-tube: Run JUnit test
:material-arrow-right:
:material-database-search: Intercept SQL queries
:material-arrow-right:
:material-table-key: Fetch index metadata
:material-arrow-right:
:material-magnify: Apply 57 rules
:material-arrow-right:
:material-alert-circle: Report & fail

QueryAudit hooks into your test's DataSource via a lightweight proxy. During test execution, it captures every SQL statement. After the test completes, it:

  1. Parses each query to identify tables, columns, joins, and clauses
  2. Fetches index metadata from your database (MySQL SHOW INDEX or PostgreSQL pg_catalog)
  3. Cross-references the query structure against actual indexes
  4. Applies 57 detection rules covering N+1, missing indexes, DML safety, locking risks, and more
  5. Produces a structured report with severity, root cause, and fix suggestions

No runtime agents. No production overhead. Just add an annotation.


See It in Action

Add a single annotation to any test class:

@SpringBootTest
@QueryAudit // (1)!
class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Test
    void findOrdersWithItems() {
        List<Order> orders = orderService.findAllWithItems();
        assertThat(orders).hasSize(5);
    }
}
  1. That's it. No configuration, no extra beans, no proxy wiring.

Run your tests, and QueryAudit produces a report like this:

================================================================================
                          QUERY AUDIT REPORT
                    OrderServiceTest (8 queries analyzed)
================================================================================

CONFIRMED ISSUES (action required)
────────────────────────────────────────────────────────────────────────────────

[ERROR] N+1 Query Detected
  Repeated query: select * from order_items where order_id = ?
  Executions:     5 times (threshold: 3)
  Suggestion:     Use JOIN FETCH or @EntityGraph to load order_items
                  with the parent query.

[ERROR] Missing Index
  Query:   select * from order_items where order_id = ?
  Table:   order_items
  Column:  order_id
  Suggestion: CREATE INDEX idx_order_items_order_id
              ON order_items (order_id);

[WARNING] Repeated single-row INSERT should use batch insert
  Query:   insert into orders (...) values (?, ?, ?)
  Table:   orders
  Detail:  Single-row INSERT executed 10 times. Each INSERT causes a
           separate network round-trip and log flush.
  Suggestion: Use batch INSERT (saveAll() in JPA with hibernate.jdbc.batch_size).

────────────────────────────────────────────────────────────────────────────────
INFO (for review)
────────────────────────────────────────────────────────────────────────────────

[WARNING] SELECT * Usage
  Query:   select * from orders where user_id = ?
  Table:   orders
  Suggestion: List only the columns you need

================================================================================
  3 confirmed issues | 1 info | 8 queries
================================================================================

CONFIRMED vs INFO

CONFIRMED issues are structural problems -- missing indexes, N+1 patterns, unsafe DML. These will cause performance issues at scale and should be fixed.

INFO items are best-practice suggestions like avoiding SELECT *. They won't fail your build by default, but are worth reviewing.


Why QueryAudit?

Most teams discover query performance problems in one of two painful ways: a production incident, or a slow code review where someone manually checks SQL logs.

Existing tools help with observability -- they let you see your queries:

Tool What it does What it doesn't do
datasource-proxy Logs queries and execution time No analysis, no detection
p6spy Logs queries with bind parameters Same -- logging only
Spring Hibernate statistics Counts queries per session Counts, but doesn't analyze

QueryAudit closes the gap. It doesn't just log queries -- it applies 57 detection rules to every captured query, cross-references index metadata from your database (MySQL SHOW INDEX or PostgreSQL pg_catalog), and produces a structured report with concrete fix suggestions.

Capability datasource-proxy p6spy QueryAudit
Query logging
Bind parameter capture
N+1 detection
Missing index detection
DML anti-pattern detection
SQL anti-pattern detection
Query count regression
Fix suggestions
CI/CD fail-on-issue

Getting Started

Ready to add QueryAudit to your project? It takes about two minutes.

Installation Guide