Proposal to standardize the Level 2 query cache configuration in JPA 2.0

Level 2 cache is one of the most powerful features of the JPA spec. It’s a transparent layer that manages out-of-session data access and cranks-up the performance of the data access tier. To my knowledge it has been first seen in Hibernate and was later adopted by the then-emerging JPA spec (driven mostly by the Hibernate guys back in the day).
As annotations gained strength and adoption, L2 caching that was initially configured through XML or properties files, was brought closer to the source code, alas in different forms and shapes. This becomes apparent if you ever have to deploy your webapp across a multitude of containers as you have to painfully recode the cache configuration (or worse, hand-coded cache access). Why not standardizing the cache control in JPA? This seems to be simple enough to achieve and yet it isn’t there. Now that JPA 2.0 is standardizing on Level 2 cache access (See JSR 317 section 6.10) it is the natural thing to do.
Every JPA provider has its own way of specifying cache access (both Entity and query cache).
To grasp the extent of the various ways cache is configured, here are some examples:

Cache control for entities

@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Employee {...

Cache control for named queries:

query="select emp from Employee emp where emp.department = ?1",


@org.hibernate.annotations.NamedQuery(cacheable=true, cacheRegion="employeeRegion")

Cache control for entities

public class Employee {...

Query cache requires hand coded cache access:

OpenJPAEntityManagerFactory oemf = OpenJPAPersistence.cast(emf);
QueryResultCache qcache = oemf.getQueryResultCache();

Cache control for entities

public class Employee {...

Query cache is hand coded (example from here):

ReadObjectQuery query = new ReadObjectQuery(Employee.class);

// Instruct the ReadQuery to cache its query results

// The first time you invoke it, the ReadQuery reads from the database, session
// cache, or both and stores the result set in its internal query cache
Employee employeeFirst = (Employee) session.executeQuery(query);

Another JPA provider, DataNucleus is used by Google AppEngine for Java and they have thir own way of defining the cache access.
Entity cache control in DataNucleus:

@javax.jdo.annotations.Extension(vendorName="datanucleus", key="cacheable", value="false")
public class Employee {...

Query caching is again manual:

query.setHint("datanucleus.query.resultCacheType", "weak");

Looking at the above examples it looks to me that caching was an afterthought for most of the JPA providers. Let’s remember that the cache should be transparent and pluggable and the value that a cache provider delivers stays in performance and not in configuration.
JPA cache configuration is in need of standardization and in my opinion the closest to a standard is (again) Hibernate.

A great addition to the JPA2 spec is the introduction of @Cacheable that controls entity caching but there’s still no standard on how to control query caching.
Here are some enhancements that I’m proposing:

1. An enhancement to @NamedQuery and @NamedNativeQuery to allow explicit cache enablement through a boolean attribute called cacheable, the same way @org.hibernate.annotations.NamedQuery is defined.

@NamedQuery(name="findEmployeesInDept", query="...", cacheable=true)
public class Employee {...

2. Both @javax.persistence.Cacheable and @NamedQuery to support optional attributes: timeout (in sec.) and a cacheRegion attribute that in turn, will point to a cache policy where a more complex configuration can be used (to add the cache concurrency strategy and eviction policy for instance).

@NamedQuery(name="findEmployeesInDepartment", query="...",
public class Employee {...

Cache regions can be configured through a new annotation, @java.persistence.annotation.CacheRegion.

public @interface Cacheable {
    boolean value() default true;
    int timeout();
    String cacheRegion();  //takes precedence if used

package java.persistence.annotation;
@Target({TYPE, PACKAGE}) @Retention(RUNTIME)
public @interface CacheRegion {
    int timeout();   //i.e. 300 s
    javax.cache.EvictionStrategy evictionStrategy(); //EvictionPolicy.LRU, FIFO, etc
    String name();  //region name
    ConcurrencyStrategy concurrencyStrategy();
    String hints();  //vendor extensions

If specified, a @CacheRegion exists either in-place along with the entity definition or separately through a new annotation @java.persistence.annotation.CacheRegions:

package java.persistence.annotation;
@Target({TYPE, PACKAGE}) @Retention(RUNTIME)
public @interface CacheRegions {
	CacheRegion[] value();

@CacheRegions ({
  CacheRegion(name="EmployeeCacheRegion", timeout=300, evictionPolicy=
EvictionStrategy.LRU, ConcurrencyStrategy.TRANSACTIONAL),
  CacheRegion(name="OfficeLocationsCacheRegion", timeout=-1, evictionPolicy=
EvictionStrategy.LRU, ConcurrencyStrategy.READ_ONLY),
public class OrganizationStructureDAO {...

4. Sensible and well-defined defaults if some of the values are nor specified: default timeout, default eviction policy and default concurrency.

Vendor extensions of cache configurations will still be able to be deployed through the hints attribute both for @NamedQuery and @CacheRegion.

In the end, configuring the cache should be executed the same way across containers given that the cache providers that will be used will likely be the same. At least that’s what I’m hoping to see in JPA 2.0.