Chapter 11. Using the JCache API Data Grid provides an implementation of the JCache (JSR-107) API that specifies a standard Java API for caching temporary Java objects in memory. Caching Java objects can help get around bottlenecks arising from using data that is expensive to retrieve or data that is hard to calculate. Caching these type of objects in memory can help speed up application performance by retrieving the data directly from memory instead of doing an expensive roundtrip or recalculation. 11.1. Creating embedded caches Prerequisites Ensure that cache-api is on your classpath. Add the following dependency to your pom.xml : <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-jcache</artifactId> </dependency> Procedure Create embedded caches that use the default JCache API configuration as follows: import javax.cache.*; import javax.cache.configuration.*; // Retrieve the system wide Cache Manager CacheManager cacheManager = Caching.getCachingProvider().getCacheManager(); // Define a named cache with default JCache configuration Cache<String, String> cache = cacheManager.createCache("namedCache", new MutableConfiguration<String, String>()); 11.1.1. Configuring embedded caches Pass the URI for custom Data Grid configuration to the CachingProvider.getCacheManager(URI) call as follows: import java.net.URI; import javax.cache.*; import javax.cache.configuration.*; // Load configuration from an absolute filesystem path URI uri = URI.create("file:///path/to/infinispan.xml"); // Load configuration from a classpath resource // URI uri = this.getClass().getClassLoader().getResource("infinispan.xml").toURI(); // Create a Cache Manager using the above configuration CacheManager cacheManager = Caching.getCachingProvider().getCacheManager(uri, this.getClass().getClassLoader(), null); Warning By default, the JCache API specifies that data should be stored as storeByValue , so that object state mutations outside of operations to the cache, won't have an impact in the objects stored in the cache. Data Grid has so far implemented this using serialization/marshalling to make copies to store in the cache, and that way adhere to the spec. Hence, if using default JCache configuration with Data Grid, data stored must be marshallable. Alternatively, JCache can be configured to store data by reference (just like Data Grid or JDK Collections work). To do that, simply call: Cache<String, String> cache = cacheManager.createCache("namedCache", new MutableConfiguration<String, String>().setStoreByValue(false)); 11.2. Store and retrieve data Even though JCache API does not extend neither java.util.Map not java.util.concurrent.ConcurrentMap , it providers a key/value API to store and retrieve data: import javax.cache.*; import javax.cache.configuration.*; CacheManager cacheManager = Caching.getCachingProvider().getCacheManager(); Cache<String, String> cache = cacheManager.createCache("namedCache", new MutableConfiguration<String, String>()); cache.put("hello", "world"); // Notice that javax.cache.Cache.put(K) returns void! String value = cache.get("hello"); // Returns "world" Contrary to standard java.util.Map , javax.cache.Cache comes with two basic put methods called put and getAndPut. The former returns void whereas the latter returns the value associated with the key. So, the equivalent of java.util.Map.put(K) in JCache is javax.cache.Cache.getAndPut(K) . Tip Even though JCache API only covers standalone caching, it can be plugged with a persistence store, and has been designed with clustering or distribution in mind. The reason why javax.cache.Cache offers two put methods is because standard java.util.Map put call forces implementors to calculate the value. When a persistent store is in use, or the cache is distributed, returning the value could be an expensive operation, and often users call standard java.util.Map.put(K) without using the return value. Hence, JCache users need to think about whether the return value is relevant to them, in which case they need to call javax.cache.Cache.getAndPut(K) , otherwise they can call java.util.Map.put(K, V) which avoids returning the potentially expensive operation of returning the value. 11.3. Comparing java.util.concurrent.ConcurrentMap and javax.cache.Cache APIs Here's a brief comparison of the data manipulation APIs provided by java.util.concurrent.ConcurrentMap and javax.cache.Cache APIs. Operation java.util.concurrent.ConcurrentMap<K, V> javax.cache.Cache<K, V> store and no return N/A void put(K key) store and return value V put(K key) V getAndPut(K key) store if not present V putIfAbsent(K key, V value) boolean putIfAbsent(K key, V value) retrieve V get(Object key) V get(K key) delete if present V remove(Object key) boolean remove(K key) delete and return value V remove(Object key) V getAndRemove(K key) delete conditional boolean remove(Object key, Object value) boolean remove(K key, V oldValue) replace if present V replace(K key, V value) boolean replace(K key, V value) replace and return value V replace(K key, V value) V getAndReplace(K key, V value) replace conditional boolean replace(K key, V oldValue, V newValue) boolean replace(K key, V oldValue, V newValue) Comparing the two APIs, it's obvious to see that, where possible, JCache avoids returning the value to avoid operations doing expensive network or IO operations. This is an overriding principle in the design of JCache API. In fact, there's a set of operations that are present in java.util.concurrent.ConcurrentMap , but are not present in the javax.cache.Cache because they could be expensive to compute in a distributed cache. The only exception is iterating over the contents of the cache: Operation java.util.concurrent.ConcurrentMap<K, V> javax.cache.Cache<K, V> calculate size of cache int size() N/A return all keys in the cache Set<K> keySet() N/A return all values in the cache Collection<V> values() N/A return all entries in the cache Set<Map.Entry<K, V>> entrySet() N/A iterate over the cache use iterator() method on keySet, values or entrySet Iterator<Cache.Entry<K, V>> iterator() 11.4. Clustering JCache instances Data Grid JCache implementation goes beyond the specification in order to provide the possibility to cluster caches using the standard API. Given a Data Grid configuration file configured to replicate caches like this: infinispan.xml <infinispan> <cache-container default-cache="namedCache"> <transport cluster="jcache-cluster" /> <replicated-cache name="namedCache" /> </cache-container> </infinispan> You can create a cluster of caches using this code: import javax.cache.*; import java.net.URI; // For multiple Cache Managers to be constructed with the standard JCache API // and live in the same JVM, either their names, or their classloaders, must // be different. // This example shows how to force their classloaders to be different. // An alternative method would have been to duplicate the XML file and give // it a different name, but this results in unnecessary file duplication. ClassLoader tccl = Thread.currentThread().getContextClassLoader(); CacheManager cacheManager1 = Caching.getCachingProvider().getCacheManager( URI.create("infinispan-jcache-cluster.xml"), new TestClassLoader(tccl)); CacheManager cacheManager2 = Caching.getCachingProvider().getCacheManager( URI.create("infinispan-jcache-cluster.xml"), new TestClassLoader(tccl)); Cache<String, String> cache1 = cacheManager1.getCache("namedCache"); Cache<String, String> cache2 = cacheManager2.getCache("namedCache"); cache1.put("hello", "world"); String value = cache2.get("hello"); // Returns "world" if clustering is working // -- public static class TestClassLoader extends ClassLoader { public TestClassLoader(ClassLoader parent) { super(parent); } }

Commands:
<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-jcache</artifactId> </dependency>
import javax.cache.*; import javax.cache.configuration.*; // Retrieve the system wide Cache Manager CacheManager cacheManager = Caching.getCachingProvider().getCacheManager(); // Define a named cache with default JCache configuration Cache<String, String> cache = cacheManager.createCache("namedCache", new MutableConfiguration<String, String>());
import java.net.URI; import javax.cache.*; import javax.cache.configuration.*; // Load configuration from an absolute filesystem path URI uri = URI.create("file:///path/to/infinispan.xml"); // Load configuration from a classpath resource // URI uri = this.getClass().getClassLoader().getResource("infinispan.xml").toURI(); // Create a Cache Manager using the above configuration CacheManager cacheManager = Caching.getCachingProvider().getCacheManager(uri, this.getClass().getClassLoader(), null);
Cache<String, String> cache = cacheManager.createCache("namedCache", new MutableConfiguration<String, String>().setStoreByValue(false));
import javax.cache.*; import javax.cache.configuration.*; CacheManager cacheManager = Caching.getCachingProvider().getCacheManager(); Cache<String, String> cache = cacheManager.createCache("namedCache", new MutableConfiguration<String, String>()); cache.put("hello", "world"); // Notice that javax.cache.Cache.put(K) returns void! String value = cache.get("hello"); // Returns "world"
<infinispan> <cache-container default-cache="namedCache"> <transport cluster="jcache-cluster" /> <replicated-cache name="namedCache" /> </cache-container> </infinispan>
import javax.cache.*; import java.net.URI; // For multiple Cache Managers to be constructed with the standard JCache API // and live in the same JVM, either their names, or their classloaders, must // be different. // This example shows how to force their classloaders to be different. // An alternative method would have been to duplicate the XML file and give // it a different name, but this results in unnecessary file duplication. ClassLoader tccl = Thread.currentThread().getContextClassLoader(); CacheManager cacheManager1 = Caching.getCachingProvider().getCacheManager( URI.create("infinispan-jcache-cluster.xml"), new TestClassLoader(tccl)); CacheManager cacheManager2 = Caching.getCachingProvider().getCacheManager( URI.create("infinispan-jcache-cluster.xml"), new TestClassLoader(tccl)); Cache<String, String> cache1 = cacheManager1.getCache("namedCache"); Cache<String, String> cache2 = cacheManager2.getCache("namedCache"); cache1.put("hello", "world"); String value = cache2.get("hello"); // Returns "world" if clustering is working // -- public static class TestClassLoader extends ClassLoader { public TestClassLoader(ClassLoader parent) { super(parent); } }