With the introduction of Singleton beans in EJB 3.1, implementing a cache in the business-tier has become easy. Only one instance of a singleton bean is created per JVM (in case the application server runs in multiple clusters) by the EJB container. The singleton beans also provide other features of EJB – transaction and concurrency management, security, remote access, life cycle callbacks, interceptors and dependency injection.
The application properties which are defined in a properties file can be accessed using a singleton bean. The properties file can be read by the singleton instance in the @PostConstruct
callback during its creation.
Since a separate singleton instance is created per JVM in a clustered application server, the changes to the shared state of one singleton bean instance are not propogated to other instances and that could lead to data inconsistency. The application server vendors can provide cluster-wide propogation of changes in a singleton bean but it is not an EJB 3.1 standard. Hence, use a read-only cache with singleton beans in a clustered application server.
The singleton bean can have no-interface view or remote or local client view like a session bean. The singleton bean is lazily initialized. It can be initialized during application startup by annotating it with @Startup
.
The container managed concurrency is provided by default in a singleton bean. The methods of a singleton bean are granted @Lock(WRITE)
– mutually exclusive lock by default. Shared lock can be obtained using @Lock(READ)
which allows multiple threads to concurrently execute the method. The bean managed concurrency can be defined using @ConcurrencyManagement(BEAN)
. While implementing bean managed concurrency we can use synchronized block/methods or Lock API.
A container-managed Timer service is provided to schedule time based events. With the inclusion of Timer in the EJB specification we no longer need to rely on non-standard scheduling frameworks like Quartz. The @Schedule
is used to automatically create a timer. The method annotated with @Schedule
acts as a callback. The timers can be persistent or non-persistent. Non-persistent timers do not survive JVM shutdown, application shutdown or server crash. A separate timer is created per JVM in a clustered application server for non-persistent timers. For persistent timers, only one timer service runs even when clustered application server is used.
A read-only cache for currency rates using a EJB 3.1 singleton bean CurrencyCacheBean
is shown below in Listing 1. The currency rates are obtained daily as an XML data feed. A cron job runs every day at 7 am to get this data feed as an HTTP GET request. The XML is parsed and currency rates are stored in the database by the cron job. The singleton bean has a timer which is scheduled to run at 7.15 am every day. The timezone of the application server on which these scheduled tasks run is applied. When this timer callback is executed, the cache is reloaded with the currencies from the database which are effectively the ones with new rates. This cache is created at the application startup and while creation of this cache all the currencies are fetched from the database and loaded in the cache. The default @Lock(WRITE)
is applicable to all methods of CurrencyCacheBean
except getCurrency(symbol)
which is annotated with @Lock(READ)
. Multiple threads can concurrently access this method to obtain the Currency
(Listing 2) by currency symbol.
@Singleton
@LocalBean
@Startup
public class CurrencyCacheBean implements CurrencyCache {
@PersistenceContext
private EntityManager em;
private ConcurrentMap<String,Currency> cache;
private void loadCurrencies() {
Query query = em.getNamedQuery(“getAllCurrencies”,Currency.class);
List currencies = query.getResultList();
for(Currency c : currencies) {
currencies.put(c.getSymbol(),c);
}
}
@Lock(READ)
public Currency getCurrency(String symbol) {
return currencies.get(symbol);
}
@PostConstruct
public void init() {
cache = new ConcurrentHashMap<String,Currency>();
loadCurrencies();
}
@Schedule(minute=”15”, hour=”7”, dayOfWeek=”*”)
public void reload() {
cache.clear();
loadCurrencies();
}
}
Listing 1. CurrencyCacheBean – Singleton bean which uses Timer
@Entity
@NamedQuery(name=”getAllCurrencies”, query=”Select c from Currency c”)
public class Currency implements Serializable {
@Id private int id;
@Column(length=3,nullable=false) private String symbol;
@Column(nullable=false) private String name;
private Double rate;
private Date rateDate;
@ManyToOne
private Currency baseCurrency;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getSymbol() { return symbol; }
public void setSymbol(String symbol) { this.symbol = symbol; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Double getRate() { return rate; }
public void setRate(Double rate) { this.rate = rate; }
public Date getRateDate() { return rateDate; }
public void setRateDate(Date rateDate) {
this.rateDate = rateDate;
}
}
Listing 2. Currency entity