Chapter 19. Caching

In almost all enterprise applications, the database is the primary bottleneck, and the least scalable tier of the runtime environment. People from a PHP/Ruby environment will try to tell you that so-called "shared nothing" architectures scale well. While that may be literally true, I don't know of many interesting multi-user applications which can be implemented with no sharing of resources between different nodes of the cluster. What these silly people are really thinking of is a "share nothing except for the database" architecture. Of course, sharing the database is the primary problem with scaling a multi-user application—so the claim that this architecture is highly scalable is absurd, and tells you a lot about the kind of applications that these folks spend most of their time working on.

Almost anything we can possibly do to share the database less often is worth doing.

This calls for a cache. Well, not just one cache. A well designed Seam application will feature a rich, multi-layered caching strategy that impacts every layer of the application:

For more information about the second-level cache, you'll need to refer to the documentation of your ORM solution, since this is an extremely complex topic. In this section we'll discuss the use of JBossCache directly, via the pojoCache component, or as the page fragment cache, via the <s:cache> control.

19.1. Using JBossCache in Seam

The built-in pojoCache component manages an instance of org.jboss.cache.aop.PojoCache. You can safely put any immutable Java object in the cache, and it will be replicated across the cluster (assuming that replication is enabled). If you want to keep mutable objects in the cache, you'll need to run the JBossCache bytecode preprocessor to ensure that changes to the objects will be automatically detected and replicated.

To use pojoCache, all you need to do is put the JBossCache jars in the classpath, and provide a resource named treecache.xml with an appropriate cache configuration. JBossCache has many scary and confusing configuration settings, so we won't discuss them here. Please refer to the JBossCache documentation for more information.

You can find a sample treecache.xml in examples/blog/resources/treecache.xml.

For an EAR depoyment of Seam, we recommend that the JBossCache jars and configuration go directly into the EAR. Make sure you place both jboss-cache.jar and jgroups.jar in your EAR's lib folder.

Now you can inject the cache into any Seam component:

@Name("chatroom")
public class Chatroom {
    @In PojoCache pojoCache;
    
    public void join(String username) {
      try
      {
         Set<String> userList = (Set<String>) pojoCache.get("chatroom", "userList");
         if (userList==null) 
         {
            userList = new HashSet<String>();
            pojoCache.put("chatroom", "userList", userList);
         }
         userList.put(username);
      }
      catch (CacheException ce)
      {
         throw new RuntimeException(ce);
      }
    }
}

If you want to have multiple JBossCache configurations in your application, use components.xml:

<core:pojo-cache name="myCache" cfg-resource-name="myown/cache.xml"/>

19.2. Page fragment caching

The most interesting user of JBossCache is the <s:cache> tag, Seam's solution to the problem of page fragment caching in JSF. <s:cache> uses pojoCache internally, so you need to follow the steps listed above before you can use it. (Put the jars in the EAR, wade through the scary configuration options, etc.)

<s:cache> is used for caching some rendered content which changes rarely. For example, the welcome page of our blog displays the recent blog entries:

<s:cache key="recentEntries-#{blog.id}" region="welcomePageFragments">
   <h:dataTable value="#{blog.recentEntries}" var="blogEntry">
      <h:column>
         <h3>#{blogEntry.title}</h3>
         <div>
            <s:formattedText value="#{blogEntry.body}"/>
         </div>
      </h:column>
   </h:dataTable>
</s:cache>

The key let's you have multiple cached versions of each page fragment. In this case, there is one cached version per blog. The region determines the JBossCache node that all version will be stored in. Different nodes may have different expiry policies. (That's the stuff you set up using the aforementioned scary configuration options.)

Of course, the big problem with <s:cache> is that it is too stupid to know when the underlying data changes (for example, when the blogger posts a new entry). So you need to evict the cached fragment manually:

public void post() {
    ...
    entityManager.persist(blogEntry);
    pojoCache.remove("welcomePageFragments", "recentEntries-" + blog.getId() );
}

Alternatively, if it is not critical that changes are immediately visible to the user, you could set a short expiry time on the JbossCache node.