I have the following scenario:
1. query cache enabled, query cache region is eternal (never evicts). My query cache is massive (entire db is cached in L2 for performance reasons).
2. queries used in the query cache make strict use of natural identifiers to avoid query cache eviction upon update/delete/insert
( see this excellent article for background on this:
http://tech.puredanger.com/2009/07/10/h ... ery-cache/ )
3. A new entity is created which matches the criteria of the cached query. Upon creation, the query cache is not evicted (because of item 2). As a result, the query cache returns stale results exclusive of the newly created entity.
The obvious solution to fix my stale query cache is to simply evict upon entity creation. However, doing so will present a huge performance issue because I deliberately cache the entire contents of the database (using a distributed terracotta/ehcache L2 cache) and want to avoid cache eviction at all costs. The query cache is delivering me a 10x performance increase, too good to ignore.
I want the query cache to be smart enough to automatically update cached query results for natural id queries when a new entity is created. This seems to me quite a (somewhat) legitimate expectation when dealing with a natural ID query where it is possible for a new entity to be assessed from the criteria and added to a cached query result without having to repeat the query at the database.
I am blurring the intended use of natural ID queries and it should not be possible for the result of a natural ID query to ever change (unless the entity is deleted), even with the creation of a new entity. If natural ids are strictly intended to uniquely identify an entity then fair enough. I'm am pushing the boundaries by using natural id based queries to define a query result that cannot change other than the deletion or addition of a new entity (since natural id fields are immutable). Natural key based queries avoid eviction and I'm exploiting this to realise big performance gains.
I have modified the store() method in my DAO used upon entity creation to manually access the query cache and update the cached result.
This appears to work for me and I am thinking about generalizing this functionality in my DAO framework and possibly suggesting this as an improvement to hibernate core. The difficulty is that it is not known at dao.store() which query caches require updating. A handle to the criteria/hql for each cached query is required to resolve the QueryKey, and the query cache region name, so that the query cache can be accessed.
Code:
//SNIP
//The following is added to my dao.store() method after the creation of a new entity 'newEntity'.
//The code here exists in the context of a hibernate session (inside a Spring JPA template execute() callback)
//resolve the cached query criteria without executing it - my criteria query has been refactored so I can get the criteria via a local private method 'getQueryCachedCriteria'
CriteriaImpl criteriaImpl = (CriteriaImpl) getQueryCachedCriteria(session,newEntity.getNaturalIdentifierField());
//resolve runtime attributes of the criteria necessary to access the query cache - i have a helper class to perform the necessary magic
Type[] resultTypes = helper.getResultTypes(sessionFactoryImpl,criteriaImpl,entityClass.getName());
Set querySpaces = helper.getQuerySpaces(sessionFactoryImpl,criteriaImpl, entityClass.getName());
String sqlString = helper.getSqlString(sessionFactoryImpl, criteriaImpl, entityClass.getName());
QueryParameters queryParameters = helper.getQueryParameters(sessionFactoryImpl, criteriaImpl,entityClass.getName());
Set filterKeys = FilterKey.createFilterKeys((SessionImplementor)session).getLoadQueryInfluencers().getEnabledFilters(),session.getEntityMode());
QueryKey key = QueryKey.generateQueryKey(sqlString, queryParameters, filterKeys, session);
//get a handle on the query cache
QueryCache queryCache = sessionFactoryImpl.getQueryCache(criteriaImpl.getCacheRegion());
//get the current result from the cache
List result = queryCache.get(key, resultTypes, true, querySpaces, session);
if(result==null){
//The query has not been cached so we don't need to do anything here.
//The next time the query runs it will be resolved from the db and added to the cache
//including the new entry.
return;
}
//update the result,
if(!result.contains(newEntity)){
result.add(newEntity);
//then put the result back in the cache (update)
if ( session.getCacheMode().isPutEnabled() ) {
boolean put = queryCache.put( key,resultTypes, result, queryParameters.isNaturalKeyLookup(), session );
}
}
//SNIP
//here is my natural id criteria:
public Criteria getQueryCachedCriteria(
org.hibernate.Session session,
Entity entity){
Criteria criteria = session.createCriteria(persistantClass, "e");
NaturalIdentifier naturalIdentifier = Restrictions.naturalId()
.set("e.naturalIdentifierField", entity.getSomeField());
criteria.add(naturalIdentifier)
.setCacheable(true)
.setCacheRegion("query.query1");
return criteria;
}
I am wondering whether anybody else has the same or similar requirement and has any advice.
Thanks!
Simon