Cayenne provides a way to cache query results, avoiding unneeded database trips for the frequently used queries. Caching strategy is configured per query. A strategy can be set via API or in CayenneModeler. Possible strategies, as defined in the org.apache.cayenne.query.QueryCacheStrategy enum are the following:

Policy Cache Scope Cache Behavior
(default) NO_CACHE N/A Always fetch, never use cache, never save to cache
LOCAL_CACHE ObjectContext If result is previously cached, use it, otherwise do a fetch and store result in cache for future use
LOCAL_CACHE_REFRESH ObjectContext Never use cache, always do a fetch and store result in cache for future use
SHARED_CACHE DataDomain (usually shared by all contexts in the same JVM) If result is previously cached, use it, otherwise do a fetch and store result in cache for future use
SHARED_CACHE_REFRESH DataDomain (usually shared by all contexts in the same JVM) Never use cache, always do a fetch and store result in cache for future use

It is important to understand that caching of result lists is done independently from caching of individual DataObjects and DataRows. Therefore the API is different as well. Also cached results lists are not synchronized across VMs (even the shared cache).

API for Result Caching

When creating queries in the code, users may set a desired cache strategy per query. Below we will create a query and set its caching policy to LOCAL_CACHE:

SelectQuery query = new SelectQuery(Artist.class);

// set local cache strategy, meaning the cache will be stored in the ObjectContext 
// and not shared between different contexts
query.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);

ObjectContext context = ... // assume this exists

// if there was no cache at this point, the query will hit the database, 
// and will store the result in the cache
List objects = context.performQuery(query);

Now if we rerun the same query (or create a new instance of the query with the same set of parameters), we'll get cached result, which will be much faster than fetching it from DB.

The point about 2 separate queries reusing the same cache entry is worth repeating. A cache key for each query is automatically generated by Cayenne based on the type of the query and its parameters (such as qualifier, ordering, etc.). So a query itself does not need to be cached by the user code for future reuse. New queries can be created as needed.

// creating a new query, same as the previous one
SelectQuery query1 = new SelectQuery(Artist.class);

// this will hit the local cache
List objects1 = context.performQuery(query1);

Or if we want to refresh the cache, but still keep caching the result after that:

query1.setCachePolicy(QueryCacheStrategy.LOCAL_CACHE_REFRESH);
List objects2 = context.performQuery(query1);

The example above shows caching with SelectQuery, but it works exactly the same way for SQLTemplate and ProcedureQuery. Similarly SHARED_CACHE and SHARED_CACHE_REFRESH cache policies create cache shared by all ObjectContexts that work on top of a given DataDomain.

There's another optional query property called "cacheGroups" that allows to fine-tune cache expiration in a declarative fashion. When creating a query, a user would specify the names of the cache groups (which are really cache expiration groups) associated with this query, and then separately define expiration policies for the cache groups present in the application. See Query Result Caching for more details.

Queries Mapped in CayenneModeler

Named queries created in CayenneModeler can also be configured to use caching, with the same cache strategy and cache groups parameters:

NamedQueries that are using caching can be executed just like any other NamedQuery.