Hibernate Criteria含日期条件时查询缓存失效问题排查
between Predicate Using new Date() The Problem
You're working with Hibernate 5.3.12.Final, and you've noticed your query cache works perfectly when you remove the between predicate in your Criteria API query—but misses every time that predicate is included. Here's your query method for reference:
public List<SomeEntity> findArticlesById(String id) { final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); final CriteriaQuery<SomeEntity> criteriaQuery = criteriaBuilder.createQuery(SomeEntity.class); final Root<SomeEntity> root = criteriaQuery.from(SomeEntity.class); final Join<SomeEntity, SomeOtherEntity> join = root.join(SomeEntity.property); criteriaQuery .select(root) .where( criteriaBuilder.equal(join.get(SomeEntity_.id), id), criteriaBuilder.between(criteriaBuilder.literal(new Date()), join.get(SomeEntity_.startDate), join.get(SomeEntity_.endDate)) ); return entityManager.createQuery(criteriaQuery) .setHint(QueryHints.HINT_CACHEABLE, true) .setHint(QueryHints.HINT_CACHE_REGION, CacheRegions.QUERY_ENTITY) .getResultList(); }
Your entities are correctly marked @Cacheable with a READ_ONLY concurrency strategy, so the cache itself is configured properly—this is confirmed by the fact that it works when you remove the between clause.
Root Cause
The issue comes down to how Hibernate generates query cache keys. Query cache keys are built from two core components:
- The exact structure of your query (the Criteria API logic in this case)
- Every parameter value used in the query
In your code, you're using criteriaBuilder.literal(new Date())—every single time you call this method, new Date() creates a fresh Date instance with the current system time (down to the millisecond). Even if you call the method just a few milliseconds apart, the Date value will be different. This means Hibernate generates a completely unique cache key for each call, so it never finds a matching entry in the query cache.
When you remove the between predicate, the only parameter is the id string. Since this value stays the same across calls, the cache key matches, and the query cache hits as expected.
Fixes
Here are two straightforward solutions to get your query cache working again:
1. Use the Database's Current Timestamp
Instead of passing a client-side new Date(), use Hibernate's built-in function to fetch the database's current timestamp. This ensures the value is consistent from the database's perspective, and won't change on every method call:
criteriaBuilder.between( criteriaBuilder.currentTimestamp(), join.get(SomeEntity_.startDate), join.get(SomeEntity_.endDate) );
2. Reuse a Single Date Instance (For Client-Side Time)
If you specifically need to use the application server's current time, extract the Date value once (e.g., at the start of the request or method scope) and reuse it across calls. This guarantees the same value is used, so the cache key remains identical:
// Capture the current time once, outside the query logic Date currentTime = new Date(); // Use the reused Date instance in your predicate criteriaBuilder.between( criteriaBuilder.literal(currentTime), join.get(SomeEntity_.startDate), join.get(SomeEntity_.endDate) );
Quick Verification
To confirm this is the root cause, enable Hibernate's cache debug logging (set org.hibernate.cache to DEBUG in your logging config). You'll see that each call with the original between predicate generates a new cache key, while calls without it reuse the same key.
内容的提问来源于stack exchange,提问作者Fabrizio Stellato

