JPA:仅当结果集不为空时才缓存查询

Posted

技术标签:

【中文标题】JPA:仅当结果集不为空时才缓存查询【英文标题】:JPA: cache queries only if resultset is not empty 【发布时间】:2021-12-06 10:24:57 【问题描述】:

我正在使用 JPA 2.1 + Hibernate + EHCache

这是我的命名查询(查询代码不相关):

List<MyEntity> list = getEntityManager()
    .createNamedQuery("my-query-id", MyEntity.class))
    .setHint(QueryHints.CACHEABLE,    true)
    .setHint(QueryHints.CACHE_REGION, "my-query-region")
    .setParameter("my-query-param", "my-param-value")
    .setMaxResults(1)
    .getResultList();

if (list.isEmpty()) 
    log.warn("No data found.");
    return null;


return list;

我希望实现的目标是仅在查询结果非空时才缓存查询结果。

我敢肯定,因为我在跟踪级别通过休眠日志记录检查了它,所以无论如何都会缓存空结果集。

任何建议都将不胜感激。

问候!

【问题讨论】:

我认为你可以直接驱逐缓存docs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/… 不幸的是,我认为这个解决方案不适合我,因为在那种情况下,我应该只驱逐与特定查询参数值相关的空条目。 但是如果你使用缓存区域,你可以通过evictQueryRegion(String regionName)来驱逐这个区域 我明白,但我们假设我使用param-value-1 执行查询,它会得到一个非空的结果集。然后我用param-value-2 再次执行它,得到一个空的结果集。因此,我在同一个区域中有一个非空结果集和一个空结果集。如果我驱逐整个区域,我也会丢失非空结果集。对吗? 你能补充一点为什么你不想缓存空结果吗? 【参考方案1】:

我通过编写 EHCache 装饰器找到了解决方案,如下所示:

EHCache XML 配置片段

<cache name="my-queries-region"
       maxEntriesLocalHeap="50000"
       eternal="false"
       timeToLiveSeconds="14400">

    <persistence strategy="none"/>

    <!-- https://www.ehcache.org/ehcache.xml -->

    <cacheDecoratorFactory
        class="com.example.JpaCacheDecoratorNotEmptyQueryFactory" />
</cache>

装饰器工厂实现

package com.example;

import net.sf.ehcache.Ehcache;
import net.sf.ehcache.constructs.CacheDecoratorFactory;

import java.util.Properties;

public class JpaCacheDecoratorNotEmptyQueryFactory extends CacheDecoratorFactory 

    @Override
    public Ehcache createDecoratedEhcache(Ehcache cache, Properties properties) 
        return new JpaCacheDecoratorNotEmptyQueryDecorator(cache);
    

    @Override
    public Ehcache createDefaultDecoratedEhcache(Ehcache cache, Properties properties) 
        return new JpaCacheDecoratorNotEmptyQueryDecorator(cache);
    

装饰器实现

package com.example;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.constructs.EhcacheDecoratorAdapter;
import org.hibernate.cache.internal.QueryResultsCacheImpl;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;

@Slf4j
public class JpaCacheDecoratorNotEmptyQueryDecorator extends EhcacheDecoratorAdapter 

    private final Field resultsField;

    @SneakyThrows
    @SuppressWarnings("rawtypes")
    protected boolean canCache(Element element) 
        boolean cacheable = true;
        Object  value     = element.getObjectValue();

        if (value instanceof QueryResultsCacheImpl.CacheItem) 
            List results = (List)resultsField.get(value);
            cacheable    = !results.isEmpty();
        

        if (!cacheable) 
            if (log.isDebugEnabled()) 
                log.debug("Query not cacheable due to empty result set.");
            
        

        return cacheable;
    

    protected boolean canCache(Collection<Element> elements) 
        for (Element element: elements) 
            if (!canCache(element)) 
                return false;
            
        

        return true;
    

    @SneakyThrows
    public JpaCacheDecoratorNotEmptyQueryDecorator(Ehcache underlyingCache) 
        super(underlyingCache);

        resultsField = QueryResultsCacheImpl
            .CacheItem
            .class
            .getDeclaredField("results");

        resultsField.setAccessible(true);
    

    @Override
    public void put(Element element, boolean doNotNotifyCacheReplicators)
        throws IllegalArgumentException,
               IllegalStateException,
               CacheException
    
        if (canCache(element)) 
            super.put(element, doNotNotifyCacheReplicators);
        
    

    @Override
    public void put(Element element)
        throws IllegalArgumentException,
               IllegalStateException,
               CacheException
    
        if (canCache(element)) 
            super.put(element);
        
    

    @Override
    public void putAll(Collection<Element> elements)
        throws IllegalArgumentException,
               IllegalStateException,
               CacheException
    
        if (canCache(elements)) 
            super.putAll(elements);
        
    

    @Override
    public void putQuiet(Element element)
        throws IllegalArgumentException,
               IllegalStateException,
               CacheException
    
        if (canCache(element)) 
            super.putQuiet(element);
        
    

    @Override
    public void putWithWriter(Element element)
        throws IllegalArgumentException,
               IllegalStateException,
               CacheException
    
        if (canCache(element)) 
            super.putWithWriter(element);
        
    

    @Override
    public Element putIfAbsent(Element element)
        throws NullPointerException
    
        if (canCache(element)) 
            return super.putIfAbsent(element);
         else 
            return null;
        
    

    @Override
    public Element putIfAbsent(Element element, boolean doNotNotifyCacheReplicators)
        throws NullPointerException
    
        if (canCache(element)) 
            return super.putIfAbsent(element, doNotNotifyCacheReplicators);
         else 
            return null;
        
    

【讨论】:

以上是关于JPA:仅当结果集不为空时才缓存查询的主要内容,如果未能解决你的问题,请参考以下文章

MSSQL - 仅当所有值都不为空时才插入值

在MyEclipse中执行模糊查询,ResultSet结果集不为空但rs.next()为false,求解。

SwiftUI:仅当输入不为空时才启用保存按钮

仅当对象在一行上不为空时才设置属性[重复]

MongoDb 仅当数组不为空时才在数组中添加字段

仅当字段不为空时才进行电子邮件验证