I am on JPA 2.1 Hibernate EHCache.
Here is my named query (the query code is not relevant):
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;
The goal I wish to achieve is to cache query result only if its result is non empty.
I am sure, beacause I inspected it by hibernate logging at a trace level, that empty result set is cached anyway.
Any suggestion would be appreciated.
Regards!
CodePudding user response:
I found a solution by writing an EHCache Decorator like follow:
EHCache XML configuration fragment
<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>
Decorator factory implementation
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);
}
}
Decorator implementation
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;
}
}
}