The WebSphere Application Server provides DynaCache component that can be used for caching. You can use DynaCache for storing output of
Servlet, portlet, JSP,... But sometimes you might want to store Custom Java Objects for caching, if that's the case you should use
CacheableCommandImpl. Ex. Lets say your application makes a SQL query to database and you want to cache output of that query, or your application makes a Web Service call and you want to cache output of that web service call(DynaCache has a feature that lets you cache service request response similar to output of servlet or portlet).
I wanted to learn how to use DynaCache for storing custom java objects so i built
SampleCacheCommand application. This application has a
SampleCacheCommandServlet servlet that takes a
ContactId has parameter and fires a SQL query to get Contact record for the supplied
ContactId and caches that contact record. So next time when you make a request to
SampleCacheCommandServlet servlet for same
ContactId, that record would be served from cache instead of going to database.
First create
CacheableContact.java like this, this object will be used for accessing the contact table data as well as for caching.
package com.webspherenotes.cache;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import com.ibm.websphere.command.CacheableCommandImpl;
public class CacheableContact extends CacheableCommandImpl {
private static final long serialVersionUID = -8098013422300089468L;
private int contactId;
private String firstName;
private String lastName;
public CacheableContact(int contactId){
this.contactId = contactId;
}
@Override
public boolean isReadyToCallExecute() {
return true;
}
@Override
public void performExecute() throws Exception {
Connection conn = null;
try {
System.out.println("Entering CacheableContact.performExecute()");
Class.forName("org.apache.derby.jdbc.ClientDriver");
conn =DriverManager.getConnection
("jdbc:derby://localhost:1527/sample;create=true", "admin", "admin");
PreparedStatement stmt = conn.prepareStatement
("SELECT * FROM ADMIN.CONTACT WHERE CONTACTID=?");
stmt.setInt(1, this.contactId);
ResultSet rs= stmt.executeQuery();
rs.next();
this.firstName =rs.getString("FIRSTNAME");
this.lastName = rs.getString("LASTNAME");
} catch (Exception e) {
e.printStackTrace(System.out);
}finally{
if(conn != null)
conn.close();
}
System.out.println("Exiting CacheableContact.performExecute()");
}
public int getContactId() {
return contactId;
}
public void setContactId(int contactId) {
this.contactId = contactId;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
@Override
public String toString() {
return "CacheableContact [contactId=" + contactId + ", firstName="
+ firstName + ", lastName=" + lastName + "]";
}
}
When your creating a cache-able object you will have to follow these rules
- Create a Java class that extends
CacheableCommandImpl class, this class provides logic to cache data by providing life cycle for your class. The CacheableCommandImpl is abstract class so in order to make a concrete class you will have to override its isReadyToCallExecute() and performExecute() method
- The
CacheableCommandImpl class provides life cycle for the cache-able object, it will call the isReadyToCallExecute() method of your class to check if your class is ready to execute. You can use this method to check if your data access part is ready. Ex. if your accessing database you can check in this method if database connection is not null, if your talking to web service then use this method to check if you can get connection to web service. If this method returns true then only the flow will move forward, if it returns false exception will be thrown
- The
performExecute() method of your class is where you write your data access code, In my case i want to make query database for supplied contactId and once the record is returned i used the values to populate instance variables of the CacheableContact class.
I guess one disadvantage of the
CacheableCommandImpl class is that it is little difficult to understand how it works but once i understood how it works in think its a very good pattern to structure your code as well. Lets take a look at the
SampleCacheCommandServlet code to see how the
CacheableContact object is used that will make it easier to understand
package com.webspherenotes.cache;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ibm.websphere.command.CommandException;
/**
* Servlet implementation class SampleCacheCommandServlet
*/
public class SampleCacheCommandServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
int contactId = Integer.parseInt(request.getParameter("contactId"));
CacheableContact cacheableContact = new CacheableContact(contactId);
try {
cacheableContact.execute();
String firstName = cacheableContact.getFirstName();
String lastName = cacheableContact.getLastName();
response.getWriter().println("
"+firstName+" " + lastName);
} catch (CommandException e) {
e.printStackTrace();
}
}
}
In the servlet first i am reading the contactId parameter supplied by user and using the contactId to create a new object of the
CacheableContact, then i am calling its
execute() method, if you look at
CacheableContact code you will notice that we are not overriding its
execute() method. The
execute() method is provided by the super class and its the place where the life cycle of cache is provided. The
execute() method checks if there is object in cache for the contactId(ContactId is primary key that we configure in the cachespec.xml) if yes it will return the
CacheableContact object in the cache for that contactId if its not there in the cache it will call
performExecute() method of
CacheableContact object and once the result is returned it will store the instance of
CacheableContact in DynaCache for the contactId.
In the
SampleCacheCommandServlet code once
execute() method is called we can assume that the
CacheableContact object is ready and use its getter methods to read firstName and lastName.
The last piece of puzzle is
cachespec.xml, This xml file is used to configure the caching of
CacheableContact object. You can create a cachespec.xml in the WEB-INF folder of your web application.
<?xml version="1.0" ?>
<!DOCTYPE cache SYSTEM "cachespec.dtd">
<cache>
<cache-entry>
<class> command</class>
<sharing-policy>not-shared</sharing-policy>
<name> com.webspherenotes.cache.CacheableContact.class</name>
<cache-id>
<component type="method" id="getContactId">
<required>true</required >
</component>
<priority>1</priority>
<timeout>180</timeout >
</cache-id>
</cache-entry>
</cache>
In the cachespec.xml first we are setting value of
class to
command, to let DynaCache know that we want to cache a custom java object. The value of
name attribute is fully qualified name of the Java object with
.class prefix.
The
cache-id element is used to define how to generate unique cache key for the cache-able object. In my case i want to use the contactId as caching key and since the DynaCache can use
getContactId() key to get the caching key i configure the key like this
<component type="method" id="getContactId">
<required>true</required >
</component>
The value of
timeout element equal to 180 means this cache entry is valid only for 180 seconds.
In the default installation of WAS or WebSPhere portal the dynamic caching service is disabled and as a result the CacheableContact object wont be cached. So use
Enabling Dynamic Caching steps to enable the dynamic caching.
After deploying the application on server try invoking a servlet for different contactIds and then you can use the DynaCacheMonitor web app to see if the record is cached or not. By default the objects will go to
baseCache instance

You can use the CacheMontior tool to even look at content of the cache entry. This part is inconsistent some time it shows only the address object and some times it shows the output of toString() method