Chapter 24. Clustered Entity EJBs

In a JBoss Enterprise Application Platform cluster, entity bean instance caches need to be kept in sync across all nodes. If an entity bean provides remote services, the service methods need to be load balanced as well.

24.1. Entity Bean in EJB 3.0

In EJB 3.0, entity beans primarily serve as a persistence data model. They do not provide remote services. Hence, the entity bean clustering service in EJB 3.0 primarily deals with distributed caching and replication, instead of load balancing.

24.1.1. Configure the distributed cache

To avoid round trips to the database, you can use a cache for your entities. JBoss EJB 3.0 entity beans are implemented by Hibernate, which has support for a second-level cache. The second-level cache provides the following functionalities:
  • If you persist a cache-enabled entity bean instance to the database via the entity manager, the entity will be inserted into the cache.
  • If you update an entity bean instance, and save the changes to the database via the entity manager, the entity will be updated in the cache.
  • If you remove an entity bean instance from the database via the entity manager, the entity will be removed from the cache.
  • If loading a cached entity from the database via the entity manager, and that entity does not exist in the database, it will be inserted into the cache.
As well as a region for caching entities, the second-level cache also contains regions for caching collections, queries, and timestamps. The Hibernate setup used for the JBoss EJB 3.0 implementation uses JBoss Cache as its underlying second-level cache implementation.
Configuration of a the second-level cache is done via your EJB3 deployment's persistence.xml, like so:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/persistence"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="tempdb" transaction-type="JTA">
    <jta-data-source>java:/DefaultDS</jta-data-source>
    <properties>
      <property name="hibernate.cache.use_second_level_cache" value="true"/>
      <property name="hibernate.cache.use_query_cache" value="true"/>
      <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.jbc2.JndiMultiplexedJBossCacheRegionFactory"/>
      <!-- region factory specific properties -->
      <property name="hibernate.cache.region.jbc2.cachefactory" value="java:CacheManager"/>
      <property name="hibernate.cache.region.jbc2.cfg.entity" value="mvcc-entity"/>
      <property name="hibernate.cache.region.jbc2.cfg.collection" value="mvcc-entity"/>
    </properties>
  </persistence-unit>
</persistence>
hibernate.cache.use_second_level_cache
Enables second-level caching of entities and collections.
hibernate.cache.use_query_cache
Enables second-level caching of queries.
hibernate.cache.region.factory_class
Defines the RegionFactory implementation that dictates region-specific caching behavior. Hibernate ships with 2 types of JBoss Cache-based second-level caches: shared and multiplexed.
A shared region factory uses the same Cache for all cache regions - much like the legacy CacheProvider implementation in older Hibernate versions.
Hibernate ships with 2 shared region factory implementations:
org.hibernate.cache.jbc2.SharedJBossCacheRegionFactory
Uses a single JBoss Cache configuration, from a newly instantiated CacheManager, for all cache regions.

Table 24.1. Additional properties for SharedJBossCacheRegionFactory

Property Default Description
hibernate.cache.region.jbc2.cfg.shared treecache.xml The classpath or file system resource containing the JBoss Cache configuration settings.
hibernate.cache.region.jbc2.cfg.jgroups.stacks org/hibernate/cache/jbc2/builder/jgroups-stacks.xml The classpath or file system resource containing the JGroups protocol stack configurations.
org.hibernate.cache.jbc2.JndiSharedJBossCacheRegionFactory
Uses a single JBoss Cache configuration, from an existing CacheManager bound to JNDI, for all cache regions.

Table 24.2. Additional properties for JndiSharedJBossCacheRegionFactory

Property Default Description
hibernate.cache.region.jbc2.cfg.shared Required JNDI name to which the shared Cache instance is bound.
A multiplexed region factory uses separate Cache instances, using optimized configurations for each cache region.

Table 24.3. Common properties for multiplexed region factory implementations

Property Default Description
hibernate.cache.region.jbc2.cfg.entity optimistic-entity The JBoss Cache configuration used for the entity cache region. Alternative configurations: mvcc-entity, pessimistic-entity, mvcc-entity-repeatable, optimistic-entity-repeatable, pessimistic-entity-repeatable
hibernate.cache.region.jbc2.cfg.collection optimistic-entity The JBoss Cache configuration used for the collection cache region. The collection cache region typically uses the same configuration as the entity cache region.
hibernate.cache.region.jbc2.cfg.query local-query The JBoss Cache configuration used for the query cache region. By default, cached query results are not replicated. Alternative configurations: replicated-query
hibernate.cache.region.jbc2.cfg.ts timestamps-cache The JBoss Cache configuration used for the timestamp cache region. If query caching is used, the corresponding timestamp cache must be replicating, even if the query cache is non-replicating. The timestamp cache region must never share the same cache as the query cache.
Hibernate ships with 2 shared region factory implementations:
org.hibernate.cache.jbc2.MultiplexedJBossCacheRegionFactory
Uses separate JBoss Cache configurations, from a newly instantiated CacheManager, per cache region.

Table 24.4. Additional properties for MultiplexedJBossCacheRegionFactory

Property Default Description
hibernate.cache.region.jbc2.configs org/hibernate/cache/jbc2/builder/jbc2-configs.xml The classpath or file system resource containing the JBoss Cache configuration settings.
hibernate.cache.region.jbc2.cfg.jgroups.stacks org/hibernate/cache/jbc2/builder/jgroups-stacks.xml The classpath or file system resource containing the JGroups protocol stack configurations.
org.hibernate.cache.jbc2.JndiMultiplexedJBossCacheRegionFactory
Uses separate JBoss Cache configurations, from a JNDI-bound CacheManager, see Section 21.2.1, “The JBoss Enterprise Application Platform CacheManager Service”, per cache region.

Table 24.5. Additional properties for JndiMultiplexedJBossCacheRegionFactory

Property Default Description
hibernate.cache.region.jbc2.cachefactory Required JNDI name to which the CacheManager instance is bound.
Now, we have JBoss Cache configured to support distributed caching of EJB 3.0 entity beans. We still have to configure individual entity beans to use the cache service.

24.1.2. Configure the entity beans for cache

Next we need to configure which entities to cache. The default is to not cache anything, even with the settings shown above. We use the @org.hibernate.annotations.Cache annotation to tag entity beans that needs to be cached.
@Entity 
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) 
public class Account implements Serializable
{
   // ... ... 
}
A very simplified rule of thumb is that you will typically want to do caching for objects that rarely change, and which are frequently read. You can fine tune the cache for each entity bean in the appropriate JBoss Cache configuration file, e.g. jboss-cache-manager-jboss-beans.xml. For instance, you can specify the size of the cache. If there are too many objects in the cache, the cache can evict the oldest or least used objects, depending on configuration, to make room for new objects. Assuming the region_prefix specified in persistence.xml was myprefix, the default name of the cache region for the com.mycompany.entities.Account entity bean would be /myprefix/com/mycompany/entities/Account.
<bean name="..." class="org.jboss.cache.config.Configuration">
   ... ...
  <property name="evictionConfig">
    <bean class="org.jboss.cache.config.EvictionConfig">
      <property name="wakeupInterval">5000</property>
      <!--  Overall default -->
      <property name="defaultEvictionRegionConfig">
        <bean class="org.jboss.cache.config.EvictionRegionConfig">
          <property name="regionName">/</property>
          <property name="evictionAlgorithmConfig">
            <bean class="org.jboss.cache.eviction.LRUAlgorithmConfig">
              <!-- Evict LRU node once we have more than this number of nodes -->
              <property name="maxNodes">10000</property>
              <!-- And, evict any node that has not been accessed in this many seconds -->
              <property name="timeToLiveSeconds">1000</property>
              <!-- Do not evict a node that's been accessed within this many seconds. 
                  Set this to a value greater than your max expected transaction length. -->
              <property name="minTimeToLiveSeconds">120</property>
            </bean>
          </property>
        </bean>
      </property>
      <property name="evictionRegionConfigs">
        <list>
          <bean class="org.jboss.cache.config.EvictionRegionConfig">
            <property name="regionName">/myprefix/com/mycompany/entities/Account</property>
            <property name="evictionAlgorithmConfig">
              <bean class="org.jboss.cache.eviction.LRUAlgorithmConfig">
                <property name="maxNodes">10000</property>
                <property name="timeToLiveSeconds">5000</property>
                <property name="minTimeToLiveSeconds">120</property>
              </bean>
            </property>
          </bean>
           ... ...
        </list>
      </property>
    </bean>
  </property>
</bean>
If you do not specify a cache region for an entity bean class, all instances of this class will be cached using the defaultEvictionRegionConfig as defined above. The @Cache annotation exposes an optional attribute "region" that lets you specify the cache region where an entity is to be stored, rather than having it be automatically created from the fully-qualified class name of the entity class.
@Entity 
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL, region = "Account") 
public class Account implements Serializable
{ 
  // ... ... 
}
The eviction configuration would then become:
<bean name="..." class="org.jboss.cache.config.Configuration">
   ... ...
  <property name="evictionConfig">
    <bean class="org.jboss.cache.config.EvictionConfig">
      <property name="wakeupInterval">5000</property>
      <!--  Overall default -->
      <property name="defaultEvictionRegionConfig">
        <bean class="org.jboss.cache.config.EvictionRegionConfig">
          <property name="regionName">/</property>
          <property name="evictionAlgorithmConfig">
            <bean class="org.jboss.cache.eviction.LRUAlgorithmConfig">
              <property name="maxNodes">5000</property>
              <property name="timeToLiveSeconds">1000</property>
              <property name="minTimeToLiveSeconds">120</property>
            </bean>
          </property>
        </bean>
      </property>
      <property name="evictionRegionConfigs">
        <list>
          <bean class="org.jboss.cache.config.EvictionRegionConfig">
            <property name="regionName">/myprefix/Account</property>
            <property name="evictionAlgorithmConfig">
              <bean class="org.jboss.cache.eviction.LRUAlgorithmConfig">
                <property name="maxNodes">10000</property>
                <property name="timeToLiveSeconds">5000</property>
                <property name="minTimeToLiveSeconds">120</property>
              </bean>
            </property>
          </bean>
           ... ...
        </list>
      </property>
    </bean>
  </property>
</bean>

24.1.3. Query result caching

The EJB3 Query API also provides means for you to save the results (i.e., collections of primary keys of entity beans, or collections of scalar values) of specified queries in the second-level cache. Here we show a simple example of annotating a bean with a named query, also providing the Hibernate-specific hints that tells Hibernate to cache the query.
First, in persistence.xml you need to tell Hibernate to enable query caching:
<property name="hibernate.cache.use_query_cache" value="true"/>
Next, you create a named query associated with an entity, and tell Hibernate you want to cache the results of that query:
 
@Entity
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL, region = "Account")
@NamedQueries(
{
  @NamedQuery(
    name = "account.bybranch",
    query = "select acct from Account as acct where acct.branch = ?1",
    hints = { @QueryHint(name = "org.hibernate.cacheable", value = "true") }
  )
})
public class Account implements Serializable
{
  // ... ... 
}
The @NamedQueries, @NamedQuery and @QueryHint annotations are all in the javax.persistence package. See the Hibernate and EJB3 documentation for more on how to use EJB3 queries and on how to instruct EJB3 to cache queries.
By default, Hibernate stores query results in JBoss Cache in a region named <region_prefix>/org/hibernate/cache/StandardQueryCache. Based on this, you can set up separate eviction handling for your query results. So, if the region prefix were set to myprefix in persistence.xml, you could, for example, create this sort of eviction handling:
<bean name="..." class="org.jboss.cache.config.Configuration">
    ... ...
   <property name="evictionConfig">
       <bean class="org.jboss.cache.config.EvictionConfig">
         <property name="wakeupInterval">5000</property>
         <!--  Overall default -->
         <property name="defaultEvictionRegionConfig">
            <bean class="org.jboss.cache.config.EvictionRegionConfig">
               <property name="regionName">/</property>
               <property name="evictionAlgorithmConfig">
                  <bean class="org.jboss.cache.eviction.LRUAlgorithmConfig">
                     <property name="maxNodes">5000</property>
                     <property name="timeToLiveSeconds">1000</property>
                     <property name="minTimeToLiveSeconds">120</property>
                  </bean>
               </property>
            </bean>
         </property>
         <property name="evictionRegionConfigs">
            <list>
               <bean class="org.jboss.cache.config.EvictionRegionConfig">
                  <property name="regionName">/myprefix/Account</property>
                  <property name="evictionAlgorithmConfig">
                     <bean class="org.jboss.cache.eviction.LRUAlgorithmConfig">
                        <property name="maxNodes">10000</property>
                        <property name="timeToLiveSeconds">5000</property>
                        <property name="minTimeToLiveSeconds">120</property>
                     </bean>
                  </property>
               </bean>
               <bean class="org.jboss.cache.config.EvictionRegionConfig">
                  <property name="regionName">/myprefix/org/hibernate/cache/StandardQueryCache</property>
                  <property name="evictionAlgorithmConfig">
                     <bean class="org.jboss.cache.eviction.LRUAlgorithmConfig">
                        <property name="maxNodes">100</property>
                        <property name="timeToLiveSeconds">600</property>
                        <property name="minTimeToLiveSeconds">120</property>
                     </bean>
                  </property>
               </bean>
            </list>
         </property>
      </bean>
   </property>
</bean>
The @NamedQuery.hints attribute shown above takes an array of vendor-specific @QueryHints as a value. Hibernate accepts the "org.hibernate.cacheRegion" query hint, where the value is the name of a cache region to use instead of the default /org/hibernate/cache/StandardQueryCache. For example:
@Entity
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL, region = "Account")
@NamedQueries(
{
  @NamedQuery(
    name = "account.bybranch",
    query = "select acct from Account as acct where acct.branch = ?1",
    hints = 
    {
      @QueryHint(name = "org.hibernate.cacheable", value = "true"),
      @QueryHint(name = "org.hibernate.cacheRegion", value = "Queries")
    }
  )
})
public class Account implements Serializable
{
  // ... ... 
}
The related eviction configuration:
<bean name="..." class="org.jboss.cache.config.Configuration">
    ... ...
   <property name="evictionConfig">
       <bean class="org.jboss.cache.config.EvictionConfig">
         <property name="wakeupInterval">5000</property>
         <!--  Overall default -->
         <property name="defaultEvictionRegionConfig">
            <bean class="org.jboss.cache.config.EvictionRegionConfig">
               <property name="regionName">/</property>
               <property name="evictionAlgorithmConfig">
                  <bean class="org.jboss.cache.eviction.LRUAlgorithmConfig">
                     <property name="maxNodes">5000</property>
                     <property name="timeToLiveSeconds">1000</property>
                     <property name="minTimeToLiveSeconds">120</property>
                  </bean>
               </property>
            </bean>
         </property>
         <property name="evictionRegionConfigs">
            <list>
               <bean class="org.jboss.cache.config.EvictionRegionConfig">
                  <property name="regionName">/myprefix/Account</property>
                  <property name="evictionAlgorithmConfig">
                     <bean class="org.jboss.cache.eviction.LRUAlgorithmConfig">
                        <property name="maxNodes">10000</property>
                        <property name="timeToLiveSeconds">5000</property>
                        <property name="minTimeToLiveSeconds">120</property>
                     </bean>
                  </property>
               </bean>
               <bean class="org.jboss.cache.config.EvictionRegionConfig">
                  <property name="regionName">/myprefix/Queries</property>
                  <property name="evictionAlgorithmConfig">
                     <bean class="org.jboss.cache.eviction.LRUAlgorithmConfig">
                        <property name="maxNodes">100</property>
                        <property name="timeToLiveSeconds">600</property>
                        <property name="minTimeToLiveSeconds">120</property>
                     </bean>
                  </property>
               </bean>
                ... ...
            </list>
         </property>
      </bean>
   </property>
</bean>