LOAD and CACHE application-scoped data with @Singleton and @Stateless
I'm looking for an elegant solutions to the old problem of loading and caching static, shared data at application startup (with an infinite lifetime).
My old way was a Spring Singleton Bean, but I'm trying now to achieve it with JAVA EE 6 (JPA2, EJB3.1, CDI).
I have an @Entity
, and an @Stateless
EJB lo load the entity from database. My thought was to add a @Singleton
EJB to cache the data; I also decided to keep the original EJB separated, to prevent violating SRP (and because in the future it might be used bypassing the cache, by other actors).
Please take a look at this simplified Proof Of Concept:
Entity
@NamedQuery(name="Room.findAll", query="SELECT r FROM Room r")
@Entity
public class Room {
@Id
private Integer id; // GETTER, SETTER
private String description; // GETTER, SETTER
}
Loader
@Stateless
public class Rooms {
@PersistenceContext
EntityManager em;
public List<Room> findAll() {
return em.createNamedQuery("Room.findAll",Room.class).getResultList();
}
}
Cacher
@Singleton
public class RoomsCached {
@EJB
Rooms rooms;
private List<Room> cachedRooms; // GETTER
@PostConstruct
public void initCache(){
this.cachedRooms = Collections.unmodifiableList(rooms.findAll());
}
}
Can you see big problems, conceptual errors or something in this example ?
My main concerns were
If both were
@Singleton
(mehh), I could have added@DependsOn("Rooms")
on the cacher bean, to ensure Rooms is already loaded before being used, but with@Singleton
and@Stateless
I can't... will@Stateless
bean always be loaded before CDI injects it into@Singleton
?@Singleton
calling@Stateless
seems weird (I've seen examples of the opposite); should I change design by putting the@Singleton
instance inside the@Stateless
EJB ?Is it right to load and cache in the
@PostConstruct
method ?
Well, I've made some tests, and I've also tried the @Decorator
way. This still seems to be the best one.
@Entity bean and @Stateless bean are the same of the question, while I've changed the @Singleton bean as follows, also adding the classic timed cache:
@Singleton
public class RoomsCached {
@Inject
Rooms rooms;
private List<Room> cachedRooms;
private long timeout = 86400000L; // reload once a day
private long lastUpdate;
public List<Room> getCachedRooms() {
initCache();
return cachedRooms;
}
public void initCache() {
if (cachedRooms == null || expired()) {
cachedRooms = Collections.unmodifiableList(rooms.findAll());
lastUpdate = System.currentTimeMillis();
}
}
private boolean expired() {
return System.currentTimeMillis() > lastUpdate + timeout;
}
}
No need to @PostConstruct, nor to @EJB, no sync issues with the underlying, @inject-ed @Stateless bean.
It's working great.