PrimeFaces supports cache at rendering time. Basically, at initial request, PrimeFaces will cache the HTML markup that corresponds to the content delimited by
<p:cache/>tag. This means that the initial request doesn't take advantage of caching, and even more it will take longer than usually since at this moment PrimeFaces caches the corresponding markup. But, at postbacks the cache will serve the cached HTML instead of rendering it again via specific renderers. This will reduce the time for loading page at postbacks.
PrimeFaces supports two different providers of cache implementation; EHCache and Hazelcast. In this post, we will take a look on the EHCache provider.
Let's suppose that we have a static table that list the tennis players from ATP. Something like in figure below (this table is produced via a simple usage of the <p:dataTable/>tag):
Rendering a table (<p:dataTable/>) is a time-consuming task, especially if the table contains many rows. So, instead of re-rendering this static table, we better cache it. In order to accomplish that via PrimeFaces and EHCache, we need to follow these steps:
1. Configure the pom.xmlto contain the needed dependencies as below:
<dependencies>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>5.3</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.1</version>
</dependency>
...
</dependencies>
2.Configure the cache provider in web.xml via the primefaces.CACHE_PROVIDER context parameter:
<context-param>
<param-name>primefaces.CACHE_PROVIDER</param-name>
<param-value>org.primefaces.cache.EHCacheProvider</param-value>
</context-param>
3. Configure EHCache. There are multiple ways to accomplish this, and one of these consist in providing the
ehcache.xml file in
/src/main/resources folder of your application. The content of this file calibrates cache as you want (more details are available
here):
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
monitoring="autodetect" dynamicConfig="true">
<!-- By default, Ehcache stored the cached files in temp folder. -->
<!-- <diskStore path="java.io.tmpdir" /> -->
<!-- Ask Ehcache to store cache in this path -->
<diskStore path="D:\\cache" />
<!-- Sample cache named myCache
This cache contains a maximum in memory of 10000 elements, and will expire
an element if it is idle for more than 5 minutes and lives for more than
10 minutes. If there are more than 10000 elements it will overflow to the
disk cache -->
<cache name="myCache"
statistics="true"
maxEntriesLocalHeap="10000"
maxEntriesLocalDisk="1000"
eternal="false"
diskSpoolBufferSizeMB="20"
timeToIdleSeconds="300" timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">
<persistence strategy="localTempSwap" />
</cache>
</ehcache>
The path D:\\cachemust be manually created, or simply modify this entry to reflect a convenient path for you.
4. Use the
<p:cache/>tag to point out the content that should be cached. As you can see in the official documentation this tag support a bunch of optional attributes. We are especially interested in the
regionattribute which allows us to point to the cache region that we want to use, which is
myCachein our case. Of course, this means that we can use
<p:cache/> with different regions. Since, by default, the
regiondefaults to view id (if you want to use it like this simply add a
<defaultCache/>region), we need to explicitly set it as below:
<p:cache region="myCache">
<p:dataTable var="t" value="#{playersBean.data}">
<p:column headerText="Player">
<h:panelGroup id="playerId">#{t.player}</h:panelGroup>
</p:column>
<p:column headerText="Age">
<h:panelGroup id="ageId">#{t.age}</h:panelGroup>
</p:column>
<p:column headerText="Birthplace">
<h:panelGroup id="birthplaceId">#{t.birthplace}</h:panelGroup>
</p:column>
<p:column headerText="Residence">
<h:panelGroup id="residenceId">#{t.residence}</h:panelGroup>
</p:column>
<p:column headerText="Height">
<h:panelGroup id="heightId">#{t.height} cm</h:panelGroup>
</p:column>
<p:column headerText="Weight">
<h:panelGroup id="weightId">#{t.weight} kg</h:panelGroup>
</p:column>
</p:dataTable>
</p:cache>
Done! If you run the application at this point then everything should work as expected. You will notice that initial request take some time to load, while postbacks will work very fast. This is a sign that, at postbacks, the table markup comes from cache.
But, how can we be sure that this is working? Well, EHCache provides management and monitoring using JMX. A simple approach consist in registering the cache statistics in the JDK platform MBeanServer, which works with the JConsolemanagement agent. The needed code is listed below:
CacheManager manager = CacheManager.create();
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ManagementService.registerMBeans(manager, mBeanServer, true, true, true, true);
We can easily slip this code in an application scoped bean that is eagerly loaded via OmniFaces
@Eager.
@Eager
@ApplicationScoped
public class CacheStatisticsBean {
private static final Logger LOG = Logger.getLogger(CacheStatisticsBean.class.getName());
private static final String CACHE_MANAGER = "net.sf.ehcache:type=CacheManager,name=__DEFAULT__";
private static final String CACHE = "net.sf.ehcache:type=Cache,CacheManager=__DEFAULT__,name=myCache";
private static final String CACHE_STATISTICS = "net.sf.ehcache:type=CacheStatistics,CacheManager=__DEFAULT__,name=myCache";
private static final String CACHE_CONFIGURATION = "net.sf.ehcache:type=CacheConfiguration,CacheManager=__DEFAULT__,name=myCache";
private static final ArrayList<ObjectName> objectNames = new ArrayList<ObjectName>() {
{
try {
add(new ObjectName(CACHE_MANAGER));
add(new ObjectName(CACHE));
add(new ObjectName(CACHE_STATISTICS));
add(new ObjectName(CACHE_CONFIGURATION));
} catch (MalformedObjectNameException ex) {
Logger.getLogger(CacheStatisticsBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
};
@PostConstruct
public void init() {
try {
LOG.info("------------ Configure JConsole MBeans ------------");
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
LOG.info("----------------- Unregister MBeans ---------------");
for (ObjectName name : objectNames) {
if (mBeanServer.isRegistered(name)) {
mBeanServer.unregisterMBean(name);
}
}
LOG.info("------------------ Register MBeans ----------------");
CacheManager manager = CacheManager.create();
ManagementService.registerMBeans(manager, mBeanServer, true, true, true, true);
LOG.info("------------ ------------------------ ------------");
} catch (NullPointerException | InstanceNotFoundException | MBeanRegistrationException ex) {
Logger.getLogger(CacheStatisticsBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Now we can perform a quick test to see if cache is working. For this, we have run the application on Payara 4. The steps of the test are:
1. Delete the content of D:\\cachefolder and ensure that Payara is not running.
2. Start the application server. For Payara 4 simply start it via asadmin start-domainform /binfolder.
3. Start JConsole. Simply navigate to your Java home and double-click on jconsole.exe in the /bin folder.
4. Connect to Payara domain as in figure below:
5. After the connection is successfully accomplished navigate to the
MBeans tab. Notice there the entry named
net.sf.ehcache. This entry was added via the
CacheStatisticsBean from above and it is what we are looking for.
At this moment, there is nothing in the cache. For checking this, simply expose some attributes under
CacheStatistics as
DiskStoreObjectCount,
MemoryStoreObjectCount,
CacheHits, etc:
6. Now, deploy and run the application. After the application starts, let's point out that at this moment we have 1 object stored in memory and on disk (check also the D:\\cachecontent) and nothing was read from cache yet:
7. Now, refresh the browser few times or open the application in multiple tabs or even other browsers. After that refresh the indicators from figure above and you should notice something like below (pay attention to a few more also):
Well, now it is obvious that our cache is working fine.
Programmatically, you can clean cache content like this:
RequestContext.getCurrentInstance().getApplicationContext().getCacheProvider().clear();