Spring JpaRepository结合CriteriaQuery使用时出现IllegalStateException: Illegal pop() with non-matching JdbcValuesSourceProcessingState异常的原因咨询
Hey Ben, let's dig into this weird intermittent exception you're facing—it's definitely a tricky one tied to Hibernate's internal state management, so let's break it down step by step.
First, let's clarify what that exception actually means: Hibernate's JdbcValuesSourceProcessingState is an internal stack used to track state when processing JDBC result sets, especially for nested or association queries. The "illegal pop()" error happens when Hibernate tries to pop a state off this stack that doesn't match the current top of the stack—essentially, its internal bookkeeping got messed up.
Now, let's tie this to your specific scenario:
Why the exception happens even with "thread-safe" EntityManager usage
You're right that Spring-managed EntityManagers are thread-bound (each transaction gets its own instance), so straight-up thread safety isn't the issue here. The problem comes from the combination of:
- Parallel calls with
REQUIRES_NEWtransactions: Each REST call spins up a brand-new, isolated transaction. While this should be safe, Hibernate 6 has edge cases around state cleanup between transactions in fast-paced parallel scenarios, especially when lazy loading is involved. - Custom CriteriaQuery implementation: If your hand-written CriteriaQuery code isn't properly scoped to the current transaction (e.g., reusing CriteriaBuilder instances across transactions, or not properly finalizing query execution), it might leave residual state in Hibernate's internal components that interferes with subsequent operations.
- Recursive lazy loading of parent entities: When you trigger lazy loading mid-transaction, Hibernate fires off a new query to fetch the parent. This adds a new state to the processing stack, and if something goes wrong with how that state is tracked (due to the above factors), the stack gets out of sync, leading to the pop() mismatch.
Why your partial fixes didn't work alone
- Switching to JpaRepository only: The default
SimpleJpaRepositoryimplementation is thread-safe, but it doesn't eliminate the lazy loading trigger. The underlying Hibernate logic for lazy loading still runs, so the same internal state mismatch can still occur in parallelREQUIRES_NEWtransactions. - Eager loading only: Even with eager loading, if your custom CriteriaQuery had residual state issues, Hibernate might still hit stack mismatches during the initial query processing (especially if the eager fetch uses nested result sets).
Why combining both fixes worked
When you switched to JpaRepository and used eager loading, you eliminated two key triggers:
- JpaRepository's standardized query handling ensures that all query state is properly scoped to the current transaction, avoiding residual state leaks.
- Eager loading fetches all parent entities in the initial query, so there's no lazy loading trigger at all—meaning Hibernate never has to push/pop additional states to the processing stack for nested queries.
Things to check if you want to avoid eager loading
If you'd prefer to keep lazy loading, try these steps to debug the root cause:
- Audit your custom CriteriaQuery code: Ensure you're always fetching the CriteriaBuilder and CriteriaQuery from the current transaction's EntityManager (never reuse instances across transactions).
- Verify transaction boundaries: Make sure all lazy loading calls happen strictly within the
REQUIRES_NEWtransaction scope—no entity should be passed outside the transaction before lazy loading is triggered. - Enable Hibernate trace logging: Turn on TRACE-level logs for
org.hibernate.sqlandorg.hibernate.resource.transactionto see exactly when transactions are created/destroyed and queries are executed. This might reveal state cleanup issues between parallel transactions.
Hope this helps you make sense of what's going on!
备注:内容来源于stack exchange,提问作者user1919452

