You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现仅缓存非空结果集的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 19:48:11