Chapter 10. Caching

10.1. Data Cache
10.1.1. Data Cache Configuration
10.1.2. Data Cache Usage
10.1.3. Query Cache
10.1.4. Third-Party Integrations
10.1.4.1. Tangosol Integration
10.1.4.2. GemStone GemFire Integration
10.1.5. Cache Extension
10.1.6. Important Notes
10.1.7. Known Issues and Limitations
10.2. Query Compilation Cache

Kodo utilizes several configurable caches to maximize performance. This chapter explores Kodo's data cache, query cache, and query compilation cache.

10.1. Data Cache

The Kodo data cache is an optional cache of persistent object data that operates at the EntityManagerFactory / PersistenceManagerFactory level. This cache is designed to significantly increase performance while remaining in full compliance with the JPA and JDO persistence standards. This means that turning on the caching option can transparently increase the performance of your application, with no changes to your code.

[Important]Important

This feature requires Kodo Enterprise Edition or Kodo Professional Edition.

Kodo's data cache is not related to the EntityManager or PersistenceManager caches dictated by the JPA and JDO persistence specifications. These specifications mandate behavior for the EntityManager and PersistenceManager caches aimed at guaranteeing transaction isolation when operating on persistent objects.

Kodo's data cache is designed to provide significant performance increases over cacheless operation, while guaranteeing that behavior will be identical in both cache-enabled and cacheless operation.

There are five ways to access data via the Kodo APIs: standard relation traversal, large result set relation traversal, queries, looking up an object by id, and iteration over an Extent. Kodo's cache plugin accelerates three of these mechanisms. It does not provide any caching of large result set relations or Extent iterators. If you find yourself in need of higher-performance Extent iteration, see Example 10.22, “Query Replaces Extent”.

Table 10.1. Data access methods

Access methodUses cache
Standard relation traversal Yes
Large result set relation traversal No
QueryYes
Lookups by object id Yes
Iteration over an Extent No

When enabled, the cache is checked before making a trip to the datastore. Data is stored in the cache when objects are committed and when persistent objects are loaded from the datastore.

Kodo's data cache can in both single-JVM and multi-JVM environments. Multi-JVM caching is achieved through the use of the distributed event notification framework described in Section 11.3, “Remote Event Notification Framework”, or through one of Kodo's integrations with third-party distributed caches (see Section 10.1.4, “Third-Party Integrations”).

The single JVM mode of operation maintains and shares a data cache across all EntityManager or PersistenceManager instances obtained from a particular EntityManagerFactory or PersistenceManagerFactory. This is not appropriate for use in a distributed environment, as caches in different JVMs or created from different factory objects will not be synchronized.

[Note]Note

If you mix and match EntityManagers and PersistenceManagers in the same application, they will use the same data cache so long as you obtain ever manager from the same factory, or as long as your EntityManagerFactory and PersistenceManagerFactory share the same underlying BrokerFactory.

10.1.1. Data Cache Configuration

To enable the basic single-factory cache set the kodo.DataCache property to true, and set the kodo.RemoteCommitProvider property to sjvm:

Example 10.1. Single-JVM Data Cache

JPA XML format:

<property name="kodo.DataCache" value="true"/>
<property name="kodo.RemoteCommitProvider" value="sjvm"/>

JDO properties format:

kodo.DataCache: true
kodo.RemoteCommitProvider: sjvm

To configure the data cache to remain up-to-date in a distributed environment, set the kodo.RemoteCommitProvider property appropriately, or integrate Kodo with a third-party caching solution. Remote commit providers are described in Section 11.3, “Remote Event Notification Framework”. Section 10.1.4, “Third-Party Integrations” enumerates supported third-party caching solutions.

Kodo's default implementation maintains a concurrent map of object ids to cache data. By default, 1000 elements are kept in cache. This can be adjusted by setting the CacheSize property in your plugin string - see below for an example. Objects that are pinned into the cache are not counted when determining if the cache size exceeds the maximum.

When the maximum cache size is exceeded, random entries are are moved to a soft reference map, so they may stick around for a little while longer. You can control the number of soft references Kodo keeps with the SoftReferenceSize property. Soft references are unlimited by default. Set to 0 to disable soft references completely.

Example 10.2. Data Cache Size

JPA XML format:

<property name="kodo.DataCache" value="true(CacheSize=5000, SoftReferenceSize=0)"/>

JDO properties format:

kodo.DataCache: true(CacheSize=5000, SoftReferenceSize=0)

Kodo offers a least-recently-used (LRU) caching option in addition to the default concurrent cache. Substitute lru for true in your kodo.DataCache setting to utilize the LRU cache. Note that while the LRU cache's eviction scheme is more optimal than the default cache's random evictions, it also requires much more synchronization. Generally, the default cache's higher concurrency results in better performance than the LRU cache's smarter eviction scheme.

[Note]Note

The default concurrent cache does not fully index its contents by class. Rather, it tracks which clasess are in the cache. It services requests to drop given classes by checking to see if any instances of that class might be in the cache, and then clearing the entire cache. You can work around this inefficiency with careful cache partitioning. The LRU cache, however, does not suffer from this limitation. Dropping a class from the LRU cache only drops entries for that class.

Example 10.3. LRU Cache

The LRU cache has the same sizing properties as the default cache.

JPA XML format:

<property name="kodo.DataCache" value="lru(CacheSize=5000, SoftReferenceSize=0)"/>

JDO properties format:

kodo.DataCache: lru(CacheSize=5000, SoftReferenceSize=0)

You can specify a cache timeout value for a class by setting the timeout metadata extension to the amount of time in milliseconds a class's data is valid. Use a value of -1 for no expiration. This is the default value.

Example 10.4. Data Cache Timeout

Timeout Employee objects after 10 seconds.

JPA:

@Entity
@DataCache(timeout=10000)
public class Employee
{
    ...
}

JDO:

<class name="Employee">
    <extension vendor-name="kodo" key="data-cache-timeout" value="10000"/>
    ...
</class>

See the org.apache.openjpa.persistence.DataCache Javadoc for more information on the DataCache annotation.

A cache can specify that it should be cleared at certain times rather than using data timeouts. The EvictionSchedule property of Kodo's cache implementation accepts a cron style eviction schedule. The format of this property is a whitespace-separated list of five tokens, where the * symbol (asterisk), indicates match all. The tokens are, in order:

  • Minute

  • Hour of Day

  • Day of Month

  • Month

  • Day of Week

For example, the following kodo.DataCache setting schedules the default cache to evict values from the cache at 15 and 45 minutes past 3 PM on Sunday.

true(EvictionSchedule='15,45 15 * * 1')

It is also possible for different persistence-capable classes to use different caches. This is achieved by specifying a cache name in a metadata extension.

Example 10.5. Named Data Cache Specification

JPA:

import org.apache.openjpa.persistence.*;

@Entity
@DataCache(name="small-cache", timeout=10000)
public class Employee
{
    ...
}

JDO:

<class name="Employee">
    <extension vendor-name="kodo" key="data-cache" value="small-cache"/>
    <extension vendor-name="kodo" key="data-cache-timeout" value="10000"/>
    ...
</class>

See the org.apache.openjpa.persistence.DataCache Javadoc for more information on the DataCache annotation.

The metadata above will cause instances of the Employee class to be stored in a cache named small-cache. This small-cache cache can be explicitly configured in the kodo.DataCache plugin string, or can be implicitly defined, in which case it will take on the same default configuration properties as the default cache identified in the kodo.DataCache property.

Example 10.6. Named Data Cache Configuration

JPA XML format:

<property name="kodo.DataCache" value="true, true(Name=small-cache, CacheSize=100)"/>

JDO properties format:

kodo.DataCache: true, true(Name=small-cache, CacheSize=100)

10.1.2. Data Cache Usage

The kodo.datacache package defines Kodo's data caching framework. While you may use this framework directly (see its Javadoc for details), its APIs are meant primarily for service providers. In fact, Section 10.1.5, “Cache Extension” below has tips on how to use this package to extend Kodo's caching service yourself.

Rather than use the low-level kodo.datacache package APIs, JPA users should typically access the data cache through Kodo's high-level org.apache.openjpa.persistence.StoreCache facade. This facade has methods to pin and unpin records, evict data from the cache, and more.

public StoreCache getStoreCache ();
public StoreCache getStoreCache (String name);

You obtain the StoreCache through the OpenJPAEntityManagerFactory.getStoreCache methods. When you have multiple data caches configured as in the small-cache example above, the StoreCache can act as a unified facade over all your caches. For every oid parameter to the StoreCache methods, it determines the correct data cache for that oid's corresponding persistent class, and dynamically delegates to that cache.

If you know that you want to access a certain data cache and no others, the OpenJPAEntityManagerFactory.getStoreCache(String name) method returns a StoreCache interface to a particular named data cache.

Example 10.7. Accessing the StoreCache

import org.apache.openjpa.persistence.*;

...

OpenJPAEntityManagerFactory oemf = OpenJPAPersistence.cast (emf);
StoreCache cache = oemf.getStoreCache ();
...
StoreCache smallCache = oemf.getStoreCache ("small-cache");
...
public void evict (Class cls, Object oid);
public void evictAll ();
public void evictAll (Class cls, Object... oids);
public void evictAll (Class cls, Collection oids);

The evict methods tell the cache to release data. Each method takes an entity class and one or more identity values, and releases the cached data for the corresponding persistent instances. The evictAll method with no arguments clears the cache. Eviction is useful when the datastore is changed by a separate process outside Kodo's control. In this scenario, you typically have to manually evict the data from the datastore cache; otherwise the Kodo runtime, oblivious to the changes, will maintain its stale copy.

public void pin (Class cls, Object oid);
public void pinAll (Class cls, Object... oids);
public void pinAll (Class cls, Collection oids);
public void unpin (Class cls, Object oid);
public void unpinAll (Class cls, Object... oids);
public void unpinAll (Class cls, Collection oids);

Most caches are of limited size. Pinning an identity to the cache ensures that the cache will mill not kick the data for the corresponding instance out of the cache, unless you manually evict it. Note that even after manual eviction, the data will get pinned again the next time it is fetched from the store. You can only remove a pin and make the data once again available for normal cache overflow eviction through the unpin methods. Use pinning when you want a guarantee that a certain object will always be available from cache, rather than requiring a datastore trip.

Example 10.8. StoreCache Usage

import org.apache.openjpa.persistence.*;

...

OpenJPAEntityManagerFactory oemf = OpenJPAPersistence.cast (emf);
StoreCache cache = oemf.getStoreCache ();
cache.pin (Magazine.class, popularMag.getId ());
cache.evict (Magazine.class, changedMag.getId ());

See the StoreCache Javadoc for information on additional functionality it provides. Also, Chapter 9, Runtime Extensions discusses Kodo's other extensions to the standard set of JPA runtime interfaces.

JDO users can access the data cache through the JDO-standard javax.jdo.datastore.DataStoreCache facade. The JDO Overview describes this interface in Chapter 13, DataStoreCache. Kodo extends the standard JDO cache in two ways:

  1. The kodo.jdo.KodoDataStoreCache interface extends JDO's DataStoreCache with many useful methods.

  2. When you have multiple data caches configured as in the small-cache example above, Kodo's DataStoreCache implementation acts as a facade over all your data caches. For every oid parameter passed to the DataStoreCache methods, it determines the correct data cache for that oid's corresponding persistent class, and dynamically delegates to that cache.

    If you know that you want to access a certain data cache and no others, the KodoPersistenceManagerFactory.getDataStoreCache(String name) method returns a JDO DataStoreCache interface to a particular named data cache.

Example 10.9. Accessing a Named Cache

import kodo.jdo.*;

...

KodoPersistenceManagerFactory kpmf = KodoJDOHelper.cast (pmf);
DataStoreCache cache = kpmf.getDataStoreCache ("small-cache");
cache.evict (JDOHelper.getObjectId (changedObj));
cache.pin (JDOHelper.getObjectId (popularObj));

You can read more about Kodo extensions to JDO APIs in Chapter 9, Runtime Extensions.

The examples above include calls to evict to manually remove data from the data cache. Rather than evicting objects from the data cache directly, you can also configure Kodo to automatically evict objects from the data cache when you use the OpenJPAEntityManager or PersistenceManager's eviction APIs.

Example 10.10. Automatic Data Cache Eviction

JPA XML format:

<property name="kodo.BrokerImpl" value="EvictFromDataCache=true"/>

JDO properties format:

kodo.BrokerImpl: EvictFromDataCache=true

JPA:

import org.apache.openjpa.persistence.*;

...

OpenJPAEntityManager oem = OpenJPAPersistence.cast (em);
oem.evict (changedMag);  // will evict from data cache also

JDO:

PersistenceManager pm = ...;
pm.evict (changedMag);  // will evict from data cache also

10.1.3. Query Cache

In addition to the data cache, the kodo.datacache package defines service provider interfaces for a query cache. The query cache is enabled by default when the data cache is enabled. The query cache stores the object ids returned by query executions. When you run a query, Kodo assembles a key based on the query properties and the parameters used at execution time, and checks for a cached query result. If one is found, the object ids in the cached result are looked up, and the resultant persistence-capable objects are returned. Otherwise, the query is executed against the database, and the object ids loaded by the query are put into the cache. The object id list is not cached until the list returned at query execution time is fully traversed.

Kodo JPA exposes a high-level interface to the query cache through the org.apache.openjpa.persistence.QueryResultCache class. You can access this class through the OpenJPAEntityManagerFactory.

Kodo JDO exposes a high-level interface to the query cache through the kodo.jdo.QueryResultCache class. You can access this class through the KodoPersistenceManagerFactory.

Example 10.11. Accessing the QueryResultCache

JPA:

import org.apache.openjpa.persistence.*;

...

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

JDO:

import kodo.jdo.*;

...

KodoPersistenceManagerFactory kpmf = KodoJDOHelper.cast (pmf);
QueryResultCache qcache = kpmf.getQueryResultCache ();

The default query cache implementation caches 100 query executions in a concurrent map. This can be changed by setting the cache size in the CacheSize plugin property. Like the data cache, the query cache also has a backing soft reference map. The SoftReferenceSize property controls the size of this map. It is disabled by default.

Example 10.12. Query Cache Size

JPA XML format:

<property name="kodo.QueryCache" value="CacheSize=1000, SoftReferenceSize=100"/>

JDO properties format:

kodo.QueryCache: CacheSize=1000, SoftReferenceSize=100

Just as Kodo offers an LRU data caching option, Kodo includes an LRU query cache as well. Once again, it has the same sizing options as the default query cache.

Example 10.13. LRU Query Cache

JPA XML format:

<property name="kodo.QueryCache" value="lru(CacheSize=1000, SoftReferenceSize=100)"/>

JDO properties format:

kodo.QueryCache: lru(CacheSize=1000, SoftReferenceSize=100)

To disable the query cache completely, set the kodo.QueryCache property to false:

Example 10.14. Disabling the Query Cache

JPA XML format:

<property name="kodo.QueryCache" value="false"/>

JDO properties format:

kodo.QueryCache: false

There are certain situations in which the query cache is bypassed:

  • Caching is not used for in-memory queries (queries in which the candidates are a collection instead of a class or Extent).

  • Caching is not used in transactions that have IgnoreChanges set to false and in which modifications to classes in the query's access path have occurred. If none of the classes in the access path have been touched, then cached results are still valid and are used.

  • Caching is not used in pessimistic transactions, since Kodo must go to the database to lock the appropriate rows.

  • Caching is not used when the the data cache does not have any cached data for an id in a query result.

  • Queries that use persistence-capable objects as parameters are only cached if the parameter is directly compared to field, as in:

    JPQL:

    select e from Employee e where e.company.address = :addr
    

    JDOQL:

    select from com.xyz.Employee where company.address == :addr
    

    If you extract field values from the parameter in your query string, or if the parameter is used in collection element comparisons, the query is not cached.

  • Queries that result in projections of custom field types or BigDecimal or BigInteger fields are not cached.

Cache results are removed from the cache when instances of classes in a cached query's access path are touched. That is, if a query accesses data in class A, and instances of class A are modified, deleted, or inserted, then the cached query result is dropped from the cache.

It is possible to tell the query cache that a class has been altered. This is only necessary when the changes occur via direct modification of the database outside of Kodo's control. You can also evict individual queries, or clear the entire cache.

JPA:

public void evict (Query q);
public void evictAll (Class cls);
public void evictAll ();

JDO:

public void evict (Query q);
public void evict (Query q, Object[] params);
public void evict (Query q, Map params);
public void evictAll (Class cls);
public void evictAll ();

For JPA queries with parameters, set the desired parameter values into the Query instance before calling the above methods.

For JDO queries with parameters, pass the parameter values of the query in the params array or map.

Example 10.15. Evicting Queries

JPA:

import org.apache.openjpa.persistence.*;

...

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

// evict all queries that can be affected by changes to Magazines
qcache.evictAll (Magazine.class);

// evict an individual query with parameters
EntityManager em = emf.createEntityManager ();
Query q = em.createQuery (...).
    setParameter (0, paramVal0).
    setParameter (1, paramVal1);
qcache.evict (q);

JDO:

import kodo.jdo.*;

...

KodoPersistenceManagerFactory kpmf = KodoJDOHelper.cast (pmf);
QueryResultCache qcache = kpmf.getQueryResultCache ();

// evict all queries that can be affected by changes to Magazines
qcache.evictAll (Magazine.class);

// evict an individual query with parameters
PersistenceManager pm = pmf.getPersistenceManager ();
Query q = pm.newQuery (...);
qcache.evictAll (q, new Object[] { paramVal0, paramVal1 });

When using one of Kodo's distributed cache implementations, it is necessary to perform this in every JVM - the change notification is not propagated automatically. When using a coherent cache implementation such as Kodo's Tangosol cache implementation, it is not necessary to do this in every JVM (although it won't hurt to do so), as the cache results are stored directly in the coherent cache.

Queries can also be pinned and unpinned through the QueryResultCache. The semantics of these operations are the same as pinning and unpinning data from the data cache.

JPA:

public void pin (Query q);
public void unpin (Query q);

JDO:

public void pin (Query q);
public void pin (Query q, Object[] params);
public void pin (Query q, Map params);
public void unpin (Query q);
public void unpin (Query q, Object[] params);
public void unpin (Query q, Map params);

For JPA queries with parameters, set the desired parameter values into the Query instance before calling the above methods.

For JDO queries with parameters, pass the parameter values of the query in the params array or map.

The following example shows these APIs in action.

Example 10.16. Pinning, and Unpinning Query Results

JPA:

import org.apache.openjpa.persistence.*;

...

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

Query pinQuery = em.createQuery (...).
    setParameter (0, paramVal0).
    setParameter (1, paramVal1);
qcache.pin (pinQuery);
Query unpinQuery = em.createQuery (...).
    setParameter (0, paramVal0).
    setParameter (1, paramVal1);
qcache.unpin (unpinQuery);

JDO:

import kodo.jdo.*;

...

KodoPersistenceManagerFactory kpmf = KodoJDOHelper.cast (pmf);
QueryResultCache qcache = kpmf.getQueryResultCache ();
PersistenceManager pm = pmf.getPersistenceManager ();

Query pinQuery = pm.newQuery (...);
qcache.pin (pinQuery, new Object[] { paramVal0, paramVal1 });
Query unpinQuery = pm.newQuery (...);
qcache.unpin (unpinQuery, new Object[] { paramVal0, paramVal1 });

Pinning data into the cache instructs the cache to not expire the pinned results when cache flushing occurs. However, pinned results will be removed from the cache if an event occurs that invalidates the results.

You can disable caching on a per-EntityManager, per-PersistenceManager or per-Query basis:

Example 10.17. Disabling and Enabling Query Caching

JPA:

import org.apache.openjpa.persistence.*;

...

// temporarily disable query caching for all queries created from em
OpenJPAEntityManager oem = OpenJPAPersistence.cast (em);
oem.getFetchPlan ().setQueryResultCache (false);

// re-enable caching for a particular query
OpenJPAQuery oq = oem.createQuery (...);
oq.getFetchPlan ().setQueryResultCache (true);

JDO:

import kodo.jdo.*;

...

// temporarily disable query caching for all queries created from pm
PersistenceManager pm = ...;
KodoFetchPlan fetch = (KodoFetchPlan) pm.getFetchPlan ();
fetch.setQueryResultCache (false);

// re-enable caching for a particular query
Query q = pm.newQuery (...);
KodoFetchPlan fetch = KodoJDOHelper.cast (pm.getFetchPlan ());
fetch.setQueryResultCache (true);

10.1.4. Third-Party Integrations

Kodo includes built-in integrations with Tangosol Coherence and GemStone GemFire caching products.

10.1.4.1. Tangosol Integration

The Kodo data cache can integrate with Tangosol's Coherence caching system. To use Tangosol integration, set the kodo.DataCache configuration property to tangosol, with the appropriate plugin properties for your Tangosol setup. For example:

Example 10.18. Tangosol Cache Configuration

JPA XML format:

<property name="kodo.DataCache" value="tangosol(TangosolCacheName=kodo)"/>

JDO properties format:

kodo.DataCache: tangosol(TangosolCacheName=kodo)

The Tangosol cache understands the following properties:

  • TangosolCacheName: The name of the Tangosol Coherence cache to use. Defaults to kodo.

  • TangosolCacheType: The type of Tangosol Coherence cache to use (optional). Valid values are named, distributed, or replicated. Defaults to named, which means that the cache is looked up via the com.tangosol.net.CacheFactory.getCache(String) method. This method looks up the cached by name as defined in the Coherence configuration.

    [Note]Note

    If you encounter problems using a Tangosol Coherence 1.2.2 distributed cache type with the Apple's OS X JVM, try using their replicated cache instead.

  • ClearOnClose: Whether the Tangosol named cache should be completely cleared when the EntityManagerFactory or PersistenceManagerFactory is closed. Defaults to false.

The Kodo query cache can also integrate with Tangosol's Coherence caching system. To use Tangosol query cache integration, set the kodo.QueryCache configuration property to tangosol, with the appropriate plugin properties for your Tangosol setup. For example:

Example 10.19. Tangosol Query Cache Configuration

JPA XML format:

<property name="kodo.QueryCache" value="tangosol(TangosolCacheName=kodo-query)"/>

JDO properties format:

kodo.QueryCache: tangosol(TangosolCacheName=kodo-query)

The Tangosol query cache understands the same properties as the data cache, with a default Tangosol cache name of kodo-query.

10.1.4.2. GemStone GemFire Integration

The Kodo data cache can integrate with GemStone's GemFire v5.0.1 caching system and later. To use GemFire in Kodo you will need to ensure that the copy-on-read attribute of the region element is set to false.

By default, the GemFire data cache will use a GemFire region of root/kodo-data-cache and the GemFire query cache will use a region of root/kodo-query-cache. This can be changed be setting the optional property GemFireCacheName.

Example 10.20. GemFire gemfire.xml example

The following example is of a gemfire.xml file that uses the default names for the query and data caches:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Gemfire cache configuration for Kodo testing -->
<!DOCTYPE cache PUBLIC
  "-//GemStone Systems, Inc.//GemFire Declarative Caching 5.0//EN"
  "http://www.gemstone.com/dtd/cache5_0.dtd">
<cache search-timeout="60" lock-lease="300" copy-on-read="false">
    <region name="root">
        <region-attributes scope="local"/>
        <region name="kodo-data-cache">
            <region-attributes scope="local"/>
        </region>
        <region name="kodo-query-cache">
            <region-attributes scope="local"/>
        </region>
    </region>
</cache>

Example 10.21. GemFire Cache Configuration

JPA persistence.xml:

<property name="kodo.DataCache" 
    value="gemfire(GemFireCacheName=/root/my-kodo-data-cache)"/>
<property name="kodo.QueryCache" 
    value="gemfire(GemFireCacheName=/root/my-kodo-query-cache)"/>

JDO properties file:

kodo.DataCache: gemfire(GemFireCacheName=/root/My-kodo-data-cache)
kodo.QueryCache: gemfire(GemFireCacheName=/root/My-kodo-query-cache)

If you set GemFire for both kodo.DataCache and kodo.QueryCache you aren't required to specify a kodo.RemoteCommitProvider unless you are registering your own RemoteCommitListeners.

Some notes regarding using GemFire with Kodo:

  • Custom field types mapped with externalizers or custom mappings must be serializable.

  • The kodo.DynamicDataStructs option is not supported.

10.1.5. Cache Extension

The provided data cache classes can be easily extended to add additional functionality. If you are adding new behavior, you should extend kodo.datacache.DataCacheImpl. To use your own storage mechanism, extend kodo.datacache.AbstractDataCache, or implement kodo.datacache.DataCache directly. If you want to implement a distributed cache that uses an unsupported method for communications, create an implementation of kodo.event.RemoteCommitProvider. This process is described in greater detail in Section 11.3.2, “Customization”.

The query cache is just as easy to extend. Add functionality by extending the default kodo.datacache.QueryCacheImpl. Implement your own storage mechanism for query results by extending kodo.datacache.AbstractQueryCache or implementing the kodo.datacache.QueryCache interface directly.

10.1.6. Important Notes

  • The default cache implementations do not automatically refresh objects in other EntityManagers and PersistenceManagers when the cache is updated or invalidated. This behavior would not be compliant with the JPA and JDO specifications.

  • Invoking OpenJPAEntityManager.evict and PersistenceManager.evict does not result in the corresponding data being dropped from the data cache, unless you have set the proper configuration options as explained above (see Example 10.10, “Automatic Data Cache Eviction”). Other methods related to the EntityManager and PersistenceManager caches also do not effect the data cache.

    The data cache assumes that it is up-to-date with respect to the datastore, so it is effectively an in-memory extension of the database. To manipulate the data cache, you should generally use the data cache facades presented in this chapter.

  • You must specify a kodo.event.RemoteCommitProvider (via the kodo.RemoteCommitProvider property) in order to use the data cache, even when using the cache in a single-JVM mode. When using it in a single-JVM context, set this property to sjvm.

10.1.7. Known Issues and Limitations

  • When using datastore (pessimistic) transactions in concert with the distributed caching implementations, it is possible to read stale data when reading data outside a transaction.

    For example, if you have two JVMs (JVM A and JVM B) both communicating with each other, and JVM A obtains a data store lock on a particular object's underlying data, it is possible for JVM B to load the data from the cache without going to the datastore, and therefore load data that should be locked. This will only happen if JVM B attempts to read data that is already in its cache during the period between when JVM A locked the data and JVM B received and processed the invalidation notification.

    This problem is impossible to solve without putting together a two-phase commit system for cache notifications, which would add significant overhead to the caching implementation. As a result, we recommend that people use optimistic locking when using data caching. If you do not, then understand that some of your non-transactional data may not be consistent with the datastore.

    Note that when loading objects in a transaction, the appropriate datastore transactions will be obtained. So, transactional code will maintain its integrity.

  • Extents are not cached. So, if you plan on iterating over a list of all the objects in an Extent on a regular basis, you will only benefit from caching if you do so with a Query instead:

    Example 10.22. Query Replaces Extent

    JPA:

    import org.apache.openjpa.persistence.*;
    
    ...
    
    OpenJPAEntityManager oem = OpenJPAPersistence.cast (em);
    Extent extent = oem.getExtent (Magazine.class, false);
    
    // This iterator does not benefit from caching...
    Iterator uncachedIterator = extent.iterator ();
    
    // ... but this one does.
    OpenJPAQuery extentQuery = oem.createQuery (...);
    extentQuery.setSubclasses (false);
    Iterator cachedIterator = extentQuery.getResultList ().iterator ();
    

    JDO:

    Extent extent = pm.getExtent (Magazine.class, false);
    
    // This iterator does not benefit from caching...
    Iterator uncachedIterator = extent.iterator ();
    
    // ... but this one does.
    Query extentQuery = pm.newQuery (extent);
    Iterator cachedIterator = ((List) extentQuery.execute ()).iterator ();