Careeroza
Query Cache
Caching Query Results
The query cache stores the result ids of a query, keyed by the query text and its parameter values. On a hit, Hibernate takes those ids and loads each entity from the L2 cache. So the query cache is only useful together with an L2-cached entity.
Enable
application.properties
spring.jpa.properties.hibernate.cache.use_query_cache=truepublic interface CategoryRepository extends JpaRepository<Category, Long> {
@QueryHints(@QueryHint(name = "org.hibernate.cacheable", value = "true"))
List<Category> findAllByOrderByNameAsc();
}Invalidation Is Per Table
Hibernate remembers the last time each table was changed (the update timestamps region). Any insert, update or delete on products through Hibernate makes every cached query that touches products stale. The next run goes to the database again.
Example
A simulation of the query cache and the per-table timestamp check:
import java.util.*;
public class Main {
record Entry(List<Long> ids, long cachedAt) {}
static long clock = 0;
static final Map<String, Long> tableUpdatedAt = new HashMap<>(Map.of("products", 0L));
static final Map<String, Entry> queryCache = new HashMap<>();
static List<Long> query(String sql) {
Entry e = queryCache.get(sql);
if (e != null && e.cachedAt() > tableUpdatedAt.get("products")) {
System.out.println("query cache HIT -> ids " + e.ids());
return e.ids();
}
System.out.println("query cache MISS -> running SQL: " + sql);
List<Long> ids = List.of(3L, 7L, 9L);
queryCache.put(sql, new Entry(ids, ++clock));
return ids;
}
static void updateProduct(long id) {
tableUpdatedAt.put("products", ++clock);
System.out.println("UPDATE products ... where id=" + id + " (products table timestamp changed)");
}
public static void main(String[] args) {
String sql = "select id from products where category_id=? [cat=2]";
query(sql);
query(sql);
updateProduct(42); // a product that is not even in the result
query(sql);
query(sql);
}
}Output
query cache MISS -> running SQL: select id from products where category_id=? [cat=2] query cache HIT -> ids [3, 7, 9] UPDATE products ... where id=42 (products table timestamp changed) query cache MISS -> running SQL: select id from products where category_id=? [cat=2] query cache HIT -> ids [3, 7, 9]
When Not to Use It
- Tables written often (orders, cart items, stock): the cache is invalidated all the time and only adds overhead.
- Queries with many different parameter values (search boxes): almost every call is a miss and memory fills with useless entries.
- Projection or DTO queries over large results: prefer an application-level cache (
@Cacheablewith Caffeine/Redis, covered in the Spring Boot material).
Tip: Rule of thumb: query cache only for small, stable lookups such as "all categories" or "all active payment methods".