Storing custom java objects in Dynacache using CachableCommand

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

  1. 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

  2. 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

  3. 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

8 comments:

  1. I have one query.The Cahce gets refeshed with in certain period of time, but while refershing if the Database is down the object will get null. then the null object is replaced in the chache. can any way we can stop the null object replacement by using Dyanchaching

    ReplyDelete
  2. like if we get the null object from the database we will not replace it. we will keep the older one or we will make again the database call till we get the data.

    ReplyDelete
  3. As mentioned the refresh time for cache, but if the values are updated before the default refresh time, it will return the stale data in that case. Please clarify

    ReplyDelete
  4. Hi,
    Can anyone help,Above application is running successfully but the object value is not showing in the CacheMonitor.Why it is not showing object value in the cache.

    ReplyDelete
  5. Hi,
    Can you please also give me an example how to clear the cache by program.

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
  7. Still data is coming from database only not from cache.Can u please suggest?

    ReplyDelete