Knowing how to build a simple and local cache for your application is an important skill. It may have a big impact on some data access performance and is quite easy to do.
This recipe will show you how to do this.
Knowing how to build a simple and local cache for your application is an important skill. It may have a big impact on some data access performance and is quite easy to do.
This recipe will show you how to do this.
Simply add a Jakarta EE dependency to your project:
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>
You need to perform the following steps to complete this recipe:
public class User {
private String name;
private String email;
//DO NOT FORGET TO IMPLEMENT THE GETTERS AND SETTERS
}
@Singleton
@Startup
public class UserCacheBean {
protected Queue<User> cache = null;
@PersistenceContext
private EntityManager em;
public UserCacheBean() {
}
protected void loadCache() {
List<User> list = em.createQuery("SELECT u FROM USER
as u").getResultList();
list.forEach((user) -> {
cache.add(user);
});
}
@Lock(LockType.READ)
public List<User> get() {
return cache.stream().collect(Collectors.toList());
}
@PostConstruct
protected void init() {
cache = new ConcurrentLinkedQueue<>();
loadCache();
}
}
Now, let's see how this recipe works.
First, let's understand our bean declaration:
@Singleton
@Startup
public class UserCacheBean {
...
@PostConstruct
protected void init() {
cache = new ConcurrentLinkedQueue<>();
loadCache();
}
}
We are using a Singleton because it has one and only one instance in the application context. This is the way we want a data cache to be – we don't want different data being shared.
Also, note that we used the @Startup annotation. This tells the server that this bean should be executed once it is loaded and that the method annotated with @PostConstruct is used for it.
So, we use the startup time to load our cache:
protected void loadCache() {
List<User> list = em.createQuery("SELECT u FROM USER
as u").getResultList();
list.forEach((user) -> {
cache.add(user);
});
}
Now, let's check the object holding our cache:
protected Queue<User> cache = null;
...
cache = new ConcurrentLinkedQueue<>();
ConcurrentLinkedQueue is a list that's built with one main purpose – to be accessed by multiple processes in a thread-safe environment. That's exactly what we need. This also offers great performance when we need to access its members.
Finally, let's check the access to our data cache:
Lock(LockType.READ)
public List<User> get() {
return cache.stream().collect(Collectors.toList());
}
We annotated the get() method with LockType.READ, which is telling the concurrency manager that it can be accessed by multiple processes at once in a thread-safe way.
If you need big and complex caches in your application, you should use some enterprise cache solutions for better results.
You can view the full source code for this recipe at https://github.com/eldermoraes/javaee8-cookbook/tree/master/chapter02/ch02-datacache.