Although this article was originally written and published earlier, we felt it was important to cover it again on our blog. JCache continues to offer exceptional advantages in enterprise computing and has been used by Tomitribe support services to improve performance of enterprise applications running TomEE.
The JCache JSR (JSR-107), finalized in late 2014, provides a standard mechanism to cache values in a map-like structure using a key. There are a number of different JCache implementations to choose from, and swapping between them should be no more difficult than swapping out .jar files on the classpath. In these examples, I will use Hazelcast which provides a distributed cache, giving the benefit that all of the nodes in the cluster are using the same cache and working consistently.
CacheManager
For anyone new to JCache, here are the basic concepts. It’s also worth having a look at the JSR-107 homepage and looking at the examples here: https://github.com/jsr107/demo
To start with, you will need to include the spec and implementation jars on your classpath. Maven users can use the dependencies below:
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>3.4.1</version>
</dependency>
Basic concepts within JCache
CachingProvider
The caching provider basically represents the implementation of JCache that you are using. You can use more than one JCache implementation in your project if you wish, and CachingProvider is how you access the different providers that are in use.
CacheManager
The CacheManager is responsible for managing and providing access to a number of named caches.
Cache
The cache actually holds the different values being cached. You can a have number of caches, each of which may be holding data for a different purpose. Each one can have a different configuration, for example, different caches may evict old data using different techniques.
Entry
Each item of data in a cache is an entry. Entries consist of a key which is a unique value used to store and lookup the data.
The key value is the actual data you wish to cache. Caches have some different properties than Maps, but the calls that you would use to store and lookup data is very similar.
Here’s the basic code for accessing a cache and storing a value – the JCache equivalent of “Hello World”, if you will.
CachingProvider cachingProvider = Caching.getCachingProvider();
CacheManager cacheManager = cachingProvider.getCacheManager();
Cache<Object, Object> cache = cacheManager.getCache("default", Object.class, Object.class);
cache.put("world", "Hello, world");
@Produces
So how do we use this in Java EE? Well, there’s actually nothing stopping us from using the code above within our application, other than it feels a bit “manual”, and will involve boilerplate code every time we need to access the cache. One easy way we could access individual caches in managed components, in our application, is to create a CDI producer, and inject the cache wherever it is needed. There are some great short CDI tutorials here: https://www.youtube.com/user/lordofthejars (if you are new to CDI.)
A simple CDI producer might look like this:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD })
public @interface NamedCache {
String name() default "defaultCache";
}
public class CacheProducer {
@Produces
@NamedCache
public Cache<Object, Object> createCache(InjectionPoint injectionPoint) {
final CachingProvider cachingProvider = Caching.getCachingProvider();
final CacheManager mgr = cachingProvider.getCacheManager();
final NamedCache annotation = injectionPoint.getAnnotated().getAnnotation(NamedCache.class);
final String name = annotation.name();
MutableConfiguration<Object, Object> config = new MutableConfiguration<Object, Object>();
config.setTypes(Object.class, Object.class);
config.setStoreByValue(true);
config.setStatisticsEnabled(true);
config.setManagementEnabled(true);
config.setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(Duration.FIVE_MINUTES));
return mgr.createCache(name, config);
}
}
Notice that we have defined a @NamedCache
annotation. This is a CDI qualifier. This allows us to create a number of differently named caches. The code above will work without it, but will return the same cache regardless of where it is injected.
With the producer above, a Cache can now be injected into a CDI bean or an EJB, or any other managed component, by using @Inject
:
@Singleton
@Lock(LockType.READ)
public class MyEJB {
@Inject
@MovieCache(name = "mycache")
private Cache<Object, Object> movieCache;
// code goes here
}
Inject CacheManager
In the previous example, we injected a Cache directly into a managed component. You may have noticed that we still manually look up the CacheManager in the CDI producer. It would be nice to have the container inject that for us as well.
At this point, I’d like to introduce a small library, JCache-CDI, that is in the Tomitribe GitHub repository. This is a very simple, Apache licensed library, that does two things. First, it provides a small, portable, CDI extension that registers the cache provider and cache manager discovered with the CDI BeanManager. Second, it also provides, via the Maven shade plugin, the CDI interceptors from the JCache reference implementation project. This library provides these as a very simple, small, single jar solution.
To use it, include the following dependency in your project:
<dependency>
<groupId>org.tomitribe</groupId>
<artifactId>jcache-cdi</artifactId>
<version>0.1-SNAPSHOT</version>
</dependency>
The CDI producer can now be modified to have the CacheManager injected straight in:
public class CacheProducer {
@Inject
private CacheManager mgr;
@Produces
@NamedCache
public Cache<Object, Object> createCache(InjectionPoint injectionPoint) {
final NamedCache annotation = injectionPoint.getAnnotated().getAnnotation(NamedCache.class);
final String name = annotation.name();
Cache<Object, Object> cache = mgr.getCache(name);
if (cache == null) {
MutableConfiguration<Object, Object> config = new MutableConfiguration<Object, Object>();
config.setTypes(Object.class, Object.class);
config.setStoreByValue(true);
config.setStatisticsEnabled(true);
config.setManagementEnabled(true);
config.setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(Duration.FIVE_MINUTES));
cache = mgr.createCache(name, config);
}
return cache;
}
}
Interceptors
Being able to Inject a Cache is great, but looking up and storing values, and executing business logic, if there is no value in the cache, still requires some fairly boilerplate code. Fortunately, the JCache JSR has defined a set of annotations to assist with that. The intention of these annotations is that they are implemented by the different Java EE application servers or CDI implementations as opposed to the JCache implementations. Java EE 7 does not include these annotations, but hopefully they will be included in Java EE 8.
No need to wait for that though, you can start using these interceptors in your code today, and your code should not require modification when these are available as part of Java EE 8. The JCache JSR provides the reference implementation of these interceptors, and these have been shaded into the Tomitribe JCache-CDI library. Using these interceptors in your code is covered below.
@CacheResult
This annotation will use the parameters of the method called to create a key, and will use that key to look up the return value in the cache. If the cache does not have the return value, the method will execute as normal, and the result will be added to the cache just before being returned to the caller. In the example below, firstResult
, maxResults
, field and searchTerm will all make up the cache key, and the List returned will be the value in the cache. Each new search will add a new entry to the cache, but if a search is repeated, the results can be returned much more quickly, avoiding the need to make a call to the database.
@Singleton
@Lock(LockType.READ)
public class MoviesBean {
@CacheResult(cacheName = "results")
public List getMovies(final Integer firstResult, final Integer maxResults, final String field, final String searchTerm) {
// JPA code to query the database
return results;
}
}
@CachePut
@CachePut
is different than @CacheResult
in that it always puts the specified value into the cache, but the method always executes as normal – the cached value does not get returned instead of executing the method body. This works well for create or update operations, where the value will always need to be written to the database, but can also be added to the cache for other functions to perform a quick lookup on.
The example below shows @CachePut
on an addMovie method that writes a row to the database. This will add the Movie to the cache, using its Id as the key.
@CachePut(cacheName = "movieById", cacheKeyGenerator = MovieIdCacheKeyGenerator.class)
public void addMovie(@CacheKey @CacheValue final Movie movie) {
entityManager.persist(movie);
}
@CacheKey, @CacheValue, CacheKeyGenerator
You probably noticed some extra annotations on that last example – @CacheKey
and @CacheValue
. These are used to specify which of the parameters to use for the key, and which one to use for the value for the cache entry.
In this specific instance, we only have a movie parameter. We want to use the whole object as the value for the cache entry and just the id field for the key. To do that, we can apply a cacheKeyGenerator
attribute on the @CachePut
annotation (the other cache operation annotations accept this parameter as well). This is an implementation of CacheKeyGenerator
. You can provide your own logic here to generate any cache key you wish. The example below demonstrates finding the Movie parameter and extracting the id field and using that to create a new DefaultGeneratedCacheKey
.
public class MovieIdCacheKeyGenerator implements CacheKeyGenerator {
@Override
public GeneratedCacheKey generateCacheKey(final CacheKeyInvocationContext<?extends Annotation>cacheKeyInvocationContext) {
final CacheInvocationParameter[] allParameters = cacheKeyInvocationContext.getAllParameters();
for (final CacheInvocationParameter parameter : allParameters) {
if (Movie.class.equals(parameter.getRawType())) {
final Movie movie = Movie.class.cast(parameter.getValue());
return new DefaultGeneratedCacheKey(new Object[]{movie.getId()});
}
}
throw new IllegalArgumentException("No movie argument found in method signature");
}
}
@CacheRemove, @CacheRemoveAll
These two annotations work in a similar way to @CachePut
, in that the operation is always applied to the cache, and the method body is executed as usual. @CacheRemove
will simply remove the relevant entry specified with @CacheKey
(remember, this can be combined with cacheKeyGenerator
if you wish) and @CacheRemoveAll
will remove all the entries in the cache.
Sample project
The sample project for this blog is available here: https://github.com/tomitribe/jcache-cdi. This has two branches, master and jcache-hazelcast. Master shows the original, unmodified project, while the jcache-hazelcast shows the project with the changes to use Hazelcast as the JCache provider, and uses the annotations shown above. The Maven build provides two profiles (mvn -Pserver1 clean install tomee:run and mvn -Pserver2 tomee:run), which will start TomEE on different ports, and Hazelcast should distribute the cache across both nodes. A shared database can be started with mvn -Pdatabase exec:java.
Useful resources
- JSR-107 GitHub page: https://github.com/jsr107
- Alex Soto’s collection of CDI tutorial videos: https://www.youtube.com/user/lordofthejars
- Tomitribe JCache-CDI project: https://github.com/tomitribe/jcache-cdi
- Moviefun example code used in this article: https://github.com/tomitribe/moviefun-jcache
- Hazelcast JCache-CDI webinar – a one hour presentation where I cover JCache, CDI and give a live demo showing how to apply these concepts to the Moviefun example project: http://hazelcast.com/resources/cluster-application-using-cdi-jcache/