It’s good to think of JCache as a set of modules, with each module delivering a specific feature. The JCache API is so split up that it’s easy and intuitive to explore it in this manner.
JCACHE MODULE/FEATURE |
API PACKAGE |
Core |
javax.cache |
Configuration |
javax.cache.configuration |
Expiration Policies |
javax.cache.expiry |
Listeners and Filters |
javax.cache.event |
Processing |
javax.cache.processing |
External Resource Integration |
javax.cache.integration |
Annotations |
javax.cache.annotation |
Management |
javax.cache.management |
SPI/Extensions |
javax.cache.spi |
Core
The APIs introduced in the Basic Building Blocks section form the bulk of what we can call the core of JCache, since these classes/interfaces (and their respective provider implementations) are fundamental to the API.
Configuration
The JCache API offers a standard set of interfaces and implementations with which one can configure a cache programmatically. The javax.cache.configuration.MutableConfiguration and its builder-like API aids in the configuration.
The following cache characteristics can be configured:
METHOD |
JCACHE CONFIGURATION ARTIFACT |
setTypes |
Data types to be stored in the Cache |
setStoreByValue |
Store by reference or value |
setExpiryPolicyFactory |
Expiration Policy |
setReadThrough, setWriteThrough |
Read through and Write through policies |
setCacheLoaderFactory, setCacheWriterFactory |
Loader and Writer implementation |
addCacheEntryListenerConfiguration |
Entry Listener implementation |
setManagementEnabled, setStatisticsEnabled |
Management and statistics activation |
MutableConfiguration<String,String> config = new MutableConfiguration();
config.setReadThrough(true);
Expiration Policies
As the name suggests, an expiry policy can be enforced on a cache, which would determine auto-eviction or removal of entries from your cache as per the policy semantics.
Note:By default, the entries in a javax.cache.Cache do not expire.
The expiry policies provided by the JCache API revolve around the javax.cache.expiry.ExpiryPolicy interface and its ready-to-use implementations.
EXPIRATION POLICY IMPLEMENTATION CLASS |
DESCRIPTION |
AccessedExpiryPolicy |
Based on time of last access |
CreatedExpiryPolicy |
Based on creation time |
EternalExpiryPolicy |
Ensures the cache entries never expire (default expiry policy) |
ModifiedExpiryPolicy |
Based on time of last update |
TouchedExpiryPolicy |
Based on time of last access OR update |
MutableConfiguration<String,String> config = new MutableConfiguration();
config.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ONE_MINUTE));
Listeners and Filters
Cache event listeners allow client code to register callbacks against the cache events they might be interested in. javax.cache.event.CacheEntryListener acts as a (parent) marker interface for other interfaces that provide a contract (method) which can be implemented in order to react to a specific event in the cache. These are typically single abstract methods, which makes them perfectly suitable for Java 8 lambda-style implementation.
JCache also has filters, which help determine whether or not to call the cache listeners. This comes in handy when you want to selectively dispatch calls to cache listeners based on certain conditions.
METHOD |
EVENT TYPE |
LISTENER INTERFACE |
onCreated |
CacheEntryEvent.CREATED |
CacheEntryCreatedListener |
onUpdated |
CacheEntryEvent.UPDATED |
CacheEntryUpdatedListener |
onExpired |
CacheEntryEvent.EXPIRED |
CacheEntryExpiredListener |
onRemoved |
CacheEntryEvent.REMOVED |
CacheEntryRemovedListener |
CacheEntryCreatedListener<Long,TicketDetails> newTicketCreationListener = (cacheEntries) -> {
for(CacheEntryEvent ticketCreatedEvent : cacheEntries){
System.out.println(“Ticket ID: “ + ticketCreatedEvent.getKey());
System.out.println(“Ticket Details: “ + ticketCreatedEvent.getValue().toString());
}
};
CacheEntryEventFilter<Long,TicketDetails> entryEventFilter = (event) -> event.getSource().getName().equals(“TicketsCache”);
External Resource Integration
The JCache API supports cache loaders and cache writers, which help integrate the cache with external resources. A read-through operation is accomplished with the help of a javax.cache. CacheLoader implementation (which is automatically invoked if a key is not found in the cache) that retrieves the value for the corresponding key from an external source. Similarly, a javax. cache.CacheWriter implementation synchronizes an external source in response to updates and removal of entries in the cache.
INTEGRATION FEATURE |
INTERFACE AND METHODS |
PRE-REQUISITE CONFIGURATION |
Read-through |
CacheLoader [load, loadAll] |
setReadThrough(true) |
Write-through |
CacheWriter [delete, deleteAll, write, writeAll] |
setWriteThrough(true) |
public class TicketCacheLoaderWriter implements CacheLoader<Long, TicketDetails>, CacheWriter<Long, TicketDetails>{
@Override
public TicketDetails load(Long ticketID) throws CacheLoaderException {
return getTicketDetails(ticketID); }
@Override
public Map<Long, TicketDetails> loadAll(Iterable<? extends Long> ticketIDs) throws CacheLoaderException {
Map<Long, TicketDetails> tickets = new HashMap<>();
for(Long ticketID : ticketIDs){
tickets.put(ticketID, getTicketDetails(ticketID));
}
return Collections.unmodifiableMap(tickets); }
private TicketDetails getTicketDetails(Long ticketID){
TicketDetails details = null;
//business logic to fetch ticket information
return details; }
@Override
public void write(Cache.Entry<? extends Long, ? extends TicketDetails> ticketEntry) throws CacheWriterException{
writeTicketDetails(ticketEntry.getKey(), ticketEntry.getValue());}
@Override
public void writeAll(Collection<Cache.Entry<? extends Long, ? extends TicketDetails>> ticketEntries) throws CacheWriterException {
for(Cache.Entry ticketEntry : ticketEntries){
writeTicketDetails((Long) ticketEntry.getKey(), (TicketDetails) ticketEntry.getValue());}
}
@Override
public void delete(Object ticketID) throws CacheWriterException {
deleteTicket((Long) ticketID); }
@Override
public void deleteAll(Collection<?> ticketIds) throws CacheWriterException {
for(Object ticketID : ticketIds){
deleteTicket((Long) ticketID); }
}
private void writeTicketDetails(Long ticketID, TicketDetails ticketDetails){
//business logic to delete ticket information
}
private void deleteTicket(Long ticketID){
}
}
Cache Entry Processing
An entry processor is particularly useful when your cache is distributed (which is quite often the case) over multiple nodes (JVMs). In order to update an existing entry in the cache, one might follow the default set of steps:
- Get the value from the cache
- Mutate/update it
- Put the updated value back into the cache
While this is perfectly normal, it is not efficient in terms of performance (especially when the cache values are large). The caching provider has to de-serialize the cache value from one of the many nodes to the client and then send the updated (and serialized) version back to the cache. The problem is magnified if multiple such calls are made in succession. Entry processors allow the client to apply a transformation on the cache entry by sending it over to the cache node rather than fetching the entry from the cache and then mutating it locally. This significantly reduces the network traffic as well as serialization/de-serialization expenses. All you need to do is define/implement the entry processor represented by the javax.cache.EntryProcessor interface and specify the same during the Cache.invoke or Cache.invokeAll methods.
METHOD |
API |
DESCRIPTION |
process |
javax.cache. EntryProcessor |
Invoked as a result of calling Cache.invoke or Cache.invokeAll |
get |
javax.cache. EntryProcessorResult |
Part of the Map returned by the Cache.invokeAll method (one for each key). It wraps the result returned by the entry processor |
Annotations
JCache annotations (in the javax.cache.annotation package) help you treat caching operations as aspects (from an aspect-oriented paradigm perspective). These annotations allow the client code to specify caching requirements in a declarative fashion. Note that the efficacy of these annotations is dependent on an external framework (like CDI, Guice, etc.), which can process these and execute the required operations.
By default, JCache exposes a limited number of caching operations via annotations, but they are useful nonetheless.
ANNOTATION |
DESCRIPTION |
@CacheResult |
Searches the cache for a key (the method parameter), invokes the method if the value cannot be found, and caches the same for future invocations |
@CachePut |
Executes a cache put with key and value in the method parameters |
@CacheRemove |
Removes a cache entry by using the key specified in the method parameter |
@CacheRemoveAll |
Removes ALL cache entries |
Note:The above-mentioned annotations are applicable on a class (which essentially enforces them for all the methods in that class) or for one or more method(s).
There are also three auxiliary annotations worth mentioning:
ANNOTATION |
DESCRIPTION |
@CacheDefaults |
Helps specify default configurations for aforementioned annotations. Applicable for a class |
@CacheKey |
Used to explicitly specify a method parameter as a cache key |
@CacheValue |
Used to explicitly specify a method parameter as a cache value when using the @CachePut annotation |
@CacheDefaults(cacheName=”TicketsCache”)
public class TicketingService{
@CachePut
public void persistTicket(long ticketID, @CacheValue TicketDetails details){
//domain logic to persist ticket information
}
@CacheResult
public TicketDetails findTicket(@CacheKey long ticketID){
TicketDetails details = null;
//execute domain logic to find ticket information
return details;
}
@CacheRemove
public void deleteTicket(@CacheKey long ticketID){
//domain logic to delete ticket information
}
@CacheRemoveAll
public void deleteAllTickets(){
//domain logic to delete ticket information
}
}
Management
JCache provides MBean interfaces whose implementations expose cache configuration and runtime performance monitoring related statistics. These statistics can be tracked through any JMX client or through the javax.management.MBeanServer API (programmatic).
MXBEAN |
OPERATIONS |
PRE-REQUISITES |
CacheMXBean |
getKeyType, getValueType, isManagementEnabled, isReadThrough, isStatisticsEnabled, isStoreByValue, isWriteThrough |
setReadThrough(true) |
CacheStatistics MXBean |
Clear, getAverageGetTime, getAveragePutTime, getAverageRemoveTime, getCacheEvictions, getCacheGets, getCacheHitPercentage, getCacheHits, getCacheMisses, getCacheMissPercentage, getCachePuts, getCacheRemovals |
setWriteThrough(true) |
MutableConfiguration<String,String> config = new MutableConfiguration();
config.setManagementEnabled(true);
config.setStatisticsEnabled(true);
SPI/Extensions
The javax.cache.spi package consists of a single interface: CachingProvider. We have already looked at the specific details of this class, but let’s understand it from a JCache vendor perspective.
A JCache provider implements this interface, and for it to be auto- discoverable, the full qualified class name of the concrete class is declared in META-INF/services/javax.cache.spi.CachingProvider—it must be available in the class path. Generally, this is packaged in the vendor implementation JAR itself. So you can think of this interface as the gateway to your JCache provider.
CacheManager cacheManager = Caching. getCachingProvider(“com.hazelcast.cache.impl.HazelcastCachingProvider”).getCacheManager();
CacheManager cacheManager = Caching. getCachingProvider(“com.tangosol.coherence.jcache.CoherenceBasedCachingProvider”).getCacheManager();
Best of Both Worlds: Using Vendor-Specific Features Along With JCache
By now I am sure you understand that JCache (JSR 107) is just a standard set of APIs that are implemented by different vendors. The standard JCache APIs provide you with a hook to tap into the concrete vendor-specific implementation itself—you can do so using the unwrap method. Let’s look at the details in the table below.
Note: This is obviously not recommended if true CachingProvider portability is what you are looking for since your application would be directly coupled to vendor-specific APIs.
METHOD |
AVAILABLE IN CLASS/INTERFACE |
DESCRIPTION |
unwrap |
javax.cache. CacheManager |
Get a handle to the CacheManager implementation of a provider |
javax.cache.Cache |
Get a handle to the Cache implementation of a provider |
javax.cache.Cache.Entry |
Get a handle to the Cache.Entry implementation of a provider |
javax.cache.annotation. CacheInvocationContext |
Get a handle to the CacheInvocationContext implementation of a provider |
//Hazelcast specific example
ICache<String,String> hazelcastICache = cache. unwrap(ICache.class);
The Hazelcast ICache extension for JCache provides lots of value added features. It’s not possible to discuss all of them in detail, but here are some of the important ones:
Asynchronous Operations: The ICache extension exposes asynchronous equivalents for most of the JCache operations like get, put, putIfAbsent, etc.
Near Cache: This feature allows Hazelcast clients (via explicit configuration) to store data locally rather than reaching out to remote Hazelcast cluster, thus reducing latency and network traffic.
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ parent.urlSource.name }}