Summary
UserRepositoryEntityGraphTest.shouldLoadRolesAndPrivilegesInBoundedQueryCountViaEntityGraphFinder is flaky. It intermittently fails with a prepared-statement count higher than the asserted bound. It is not a real regression — the query behavior is correct; the measurement is not isolated under JUnit parallel execution.
Observed failure
CI run 29130212931, Test on Java 25 (runtime compatibility) leg, on a docs-only commit (b789209):
UserRepositoryEntityGraphTest > shouldLoadRolesAndPrivilegesInBoundedQueryCountViaEntityGraphFinder FAILED
java.lang.AssertionError: [EntityGraph finder should load roles + privileges in a bounded number of queries]
Expecting actual:
7L
to be less than or equal to:
3L
at UserRepositoryEntityGraphTest.java:139
The commit was a changelog edit that cannot affect SQL, and the commits immediately before and after it passed all matrix legs — confirming flakiness rather than regression.
Root cause
The test reads a SessionFactory-global Hibernate Statistics counter:
// UserRepositoryEntityGraphTest.java:65-70
private Statistics statistics() {
EntityManager em = entityManager.getEntityManager();
Statistics stats = em.getEntityManagerFactory().unwrap(SessionFactory.class).getStatistics();
stats.setStatisticsEnabled(true);
return stats;
}
// lines 124-139
Statistics stats = statistics();
stats.clear(); // clears the GLOBAL counter
User user = userRepository.findWithRolesByEmail("bounded@test.com");
...
assertThat(stats.getPrepareStatementCount()).isLessThanOrEqualTo(3);
getPrepareStatementCount() is scoped to the whole persistence unit, not the current session/thread. The suite runs with JUnit 5 parallel execution, and @DataJpaTest classes share the same cached Spring context (and thus the same SessionFactory and Statistics object). Concurrently-running tests increment the shared counter between stats.clear() and the assertion read, so the observed count (7) exceeds the real per-query count (≤3). The counter is not transactional, so @DataJpaTest rollback does not isolate it.
Suggested fix
Make the statement count isolated per-test rather than reading a global SessionFactory statistic. Preferred approach: count statements via a thread-local StatementInspector (each parallel test runs on its own thread/JDBC connection, so a thread-local counter is naturally isolated), then assert on that count. This preserves the N+1 regression-guard value of the test without the parallel-execution pollution.
Alternatives considered (less good): pinning the class to same-thread execution does not help because other classes on other threads still mutate the shared counter; removing the count assertion loses the N+1 guard.
Notes
- Introduced by
70e1f99 perf: lazy role/privilege fetch with EntityGraph on the auth path (regression guard for the User.roles EAGER → LAZY switch).
- Low urgency:
main is currently green; this only causes intermittent red builds.
Summary
UserRepositoryEntityGraphTest.shouldLoadRolesAndPrivilegesInBoundedQueryCountViaEntityGraphFinderis flaky. It intermittently fails with a prepared-statement count higher than the asserted bound. It is not a real regression — the query behavior is correct; the measurement is not isolated under JUnit parallel execution.Observed failure
CI run 29130212931,
Test on Java 25 (runtime compatibility)leg, on a docs-only commit (b789209):The commit was a changelog edit that cannot affect SQL, and the commits immediately before and after it passed all matrix legs — confirming flakiness rather than regression.
Root cause
The test reads a
SessionFactory-global HibernateStatisticscounter:getPrepareStatementCount()is scoped to the whole persistence unit, not the current session/thread. The suite runs with JUnit 5 parallel execution, and@DataJpaTestclasses share the same cached Spring context (and thus the sameSessionFactoryandStatisticsobject). Concurrently-running tests increment the shared counter betweenstats.clear()and the assertion read, so the observed count (7) exceeds the real per-query count (≤3). The counter is not transactional, so@DataJpaTestrollback does not isolate it.Suggested fix
Make the statement count isolated per-test rather than reading a global
SessionFactorystatistic. Preferred approach: count statements via a thread-localStatementInspector(each parallel test runs on its own thread/JDBC connection, so a thread-local counter is naturally isolated), then assert on that count. This preserves the N+1 regression-guard value of the test without the parallel-execution pollution.Alternatives considered (less good): pinning the class to same-thread execution does not help because other classes on other threads still mutate the shared counter; removing the count assertion loses the N+1 guard.
Notes
70e1f99 perf: lazy role/privilege fetch with EntityGraph on the auth path(regression guard for theUser.rolesEAGER → LAZY switch).mainis currently green; this only causes intermittent red builds.