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

JPA: cache queries only if resultset is not empty

我在 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;

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

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

如有任何建议,我们将不胜感激。

此致!

我通过编写 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;
        }
    }
}