QueryCaptureContext.java
package li.selman.persistencetest.capture;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import li.selman.persistencetest.core.CapturedQuery;
/**
* Accumulates {@link CapturedQuery} instances for the current thread.
*
* <p>Backed by a thread-local rather than a single shared list: JDBC calls are synchronous on the calling
* thread, so for the common case of a test method that talks to the database directly (or through
* Hibernate/Spring Data JPA/JdbcTemplate, still synchronously on that same thread), {@link #current()}
* transparently gives each test its own isolated view with no explicit wiring, and holds up correctly under
* parallel JUnit 5 test execution and Java 25 virtual threads (each carries its own thread-locals).
*
* <p><b>Known limitation:</b> if code under test hands work off to a different thread (an {@code @Async}
* method, an executor, a reactive scheduler) and that thread executes queries, those queries are recorded
* against <em>that thread's</em> context, not the test thread's. They will not appear in
* {@link #capturedQueries()} as called from the test method. Propagating capture across thread handoffs
* would need cooperation from whatever does the handoff (e.g. wrapping the executor) and is not attempted
* here.
*/
public final class QueryCaptureContext {
private static final ThreadLocal<QueryCaptureContext> CURRENT = ThreadLocal.withInitial(QueryCaptureContext::new);
private final List<CapturedQuery> queries = new CopyOnWriteArrayList<>();
private final AtomicLong sequence = new AtomicLong();
private QueryCaptureContext() {}
/** The capture context for the calling thread. */
public static QueryCaptureContext current() {
return CURRENT.get();
}
/** Queries captured on this thread since the last {@link #reset()}, in execution order. */
public List<CapturedQuery> capturedQueries() {
return List.copyOf(queries);
}
/** Discards captured queries and restarts execution-order numbering from zero. */
public void reset() {
queries.clear();
sequence.set(0);
}
void record(CapturedQuery query) {
queries.add(query);
}
long nextSequence() {
return sequence.getAndIncrement();
}
}