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.xml
to 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:\\cache
must 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
region
attribute which allows us to point to the cache region that we want to use,
which is
myCache
in our case. Of course, this means that we can use
<p:cache/> with different regions.
Since, by default, the
region
defaults 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 JConsole
management 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:\\cache
folder and ensure that Payara is not running.
2. Start the application server. For Payara 4 simply start it via asadmin start-domain
form /bin
folder.
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:\\cache
content) 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();