如何实现仅缓存非空结果集的JPA查询?基于JPA 2.1+Hibernate+EHCache场景
解决Hibernate查询缓存只缓存非空结果的问题
我之前也碰到过一模一样的情况——Hibernate默认会把空结果集也塞进查询缓存里,要实现只缓存非空结果,这里有几个实用的方案,你可以根据自己的场景选:
方案一:手动控制查询缓存(适合单个查询场景)
这个思路很直接:先执行不带缓存的查询,拿到结果后如果是非空的,再手动把结果存入查询缓存。这样空结果就不会被缓存了。
代码示例如下:
// 第一步:执行不带缓存的查询 List<MyEntity> list = getEntityManager() .createNamedQuery("my-query-id", MyEntity.class) .setParameter("my-query-param", "my-param-value") .setMaxResults(1) .getResultList(); if (!list.isEmpty()) { // 第二步:手动将非空结果存入查询缓存 Session session = getEntityManager().unwrap(Session.class); SessionFactory sessionFactory = session.getSessionFactory(); // 获取目标查询缓存区域 QueryCache queryCache = sessionFactory.getCache().getQueryCache("my-query-region"); // 构建Hibernate识别的查询键(要和内部生成逻辑一致,否则缓存无法命中) QueryKey queryKey = QueryKey.generateQueryKey( "my-query-id", Collections.singletonMap("my-query-param", "my-param-value"), null, // 投影类型,无投影则传null MyEntity.class, session.getFlushMode(), session.getCacheMode(), sessionFactory ); // 将结果存入缓存 queryCache.put(queryKey, MyEntity.class, list, false, session); return list; } else { log.warn("No data found."); return null; }
注意:QueryKey的生成参数要和你实际查询的参数完全匹配,否则后续查询无法命中缓存。另外,当MyEntity的数据更新时,Hibernate的查询缓存会自动失效,这个逻辑和默认行为一致,不用担心数据一致性问题。
方案二:自定义QueryCache实现(适合全局多个查询场景)
如果你的项目中有很多查询都需要“只缓存非空结果”的逻辑,手动改每个查询太麻烦,可以通过自定义QueryCache来全局拦截空结果的缓存写入。
1. 实现自定义QueryCache包装类
这个类会包装Hibernate默认的QueryCache,在写入缓存前判断结果是否为空:
public class NonEmptyQueryCache implements QueryCache { private final QueryCache delegate; public NonEmptyQueryCache(QueryCache delegate) { this.delegate = delegate; } @Override public void put(QueryKey key, Type[] returnTypes, List result, boolean isNaturalKeyLookup, SessionImplementor session) throws HibernateException { // 仅当结果非空时才写入缓存 if (result != null && !result.isEmpty()) { delegate.put(key, returnTypes, result, isNaturalKeyLookup, session); } } // 其他方法直接委托给默认的QueryCache实现 @Override public List get(QueryKey key, Type[] returnTypes, boolean isNaturalKeyLookup, SessionImplementor session) throws HibernateException { return delegate.get(key, returnTypes, isNaturalKeyLookup, session); } @Override public void clear() throws HibernateException { delegate.clear(); } @Override public void destroy() { delegate.destroy(); } @Override public String getRegionName() { return delegate.getRegionName(); } }
2. 实现QueryCache工厂类
用来创建我们自定义的QueryCache实例:
public class NonEmptyQueryCacheFactory implements QueryCacheFactory { @Override public QueryCache getQueryCache(String regionName, UpdateTimestampsCache updateTimestampsCache, Settings settings, Properties props) throws HibernateException { // 创建默认的QueryCache,再用我们的类包装它 QueryCache defaultCache = new StandardQueryCache(settings, props, regionName, updateTimestampsCache); return new NonEmptyQueryCache(defaultCache); } }
3. 配置Hibernate使用自定义工厂
在你的persistence.xml或者Hibernate配置文件中添加以下配置:
<property name="hibernate.cache.query_cache_factory" value="com.yourpackage.NonEmptyQueryCacheFactory"/>
这样所有查询缓存的写入都会经过我们的自定义逻辑,自动跳过空结果的缓存。
测试验证
不管用哪个方案,都建议通过Hibernate的Trace日志验证:
- 当查询返回空结果时,日志中应该不会出现
putting query result into cache region之类的缓存写入日志 - 当查询返回非空结果时,能看到缓存写入的日志,且后续相同参数的查询会直接从缓存获取结果
内容的提问来源于stack exchange,提问作者Antonio Petricca
相关产品推荐
相关产品推荐

