Use Memcached for Java enterprise performance, Part 2: Database-driven web apps

Using Memcached with Hibernate in Java web applications

1 2 Page 2
Page 2 of 2

More configuration properties

See "Adding hibernate-memcached to your application" on the hibernate-memcached wiki for a list of all the configuration properties supported by hibernate-memcached.

Develop a caching strategy

Once you have enabled caching your next step is to choose which data you want to cache and what caching strategy you will use. In my case, I want to cache the data from my CONTACT table. Since that will be regularly updated, I set my cache strategy to read-write by adding a cache element in the Contact.hbm.xml, like this:

Listing 3. Configure the caching strategy

<hibernate-mapping package="com.javaworld.Memcached">
    <class name="Contact" table="CONTACT"  >
    <cache usage="read-write"/>
        <id name="contactId" column="CONTACTID">
            <generator class="increment"/>
        </id>
        <property name="firstName" column="FIRSTNAME"/>
        <property name="lastName" column="LASTNAME"/>
        <property name="email" column="EMAIl"/>
    </class>
</hibernate-mapping>

After you've updated your own Contact.hbm.xml file (which you can download with the article source), start the Memcached server by executing a Memcached -vv command. This will start Memcached in verbose mode so that it prints every client interaction on the console. Next, execute mvn clean install to start the application. Go to http://localhost:8080/ManageContact/contact and add couple of records. When you click on the record to go to the details, you should notice that no SQL query is executed; instead those records are coming back from the cache. On the Memcached server console, you should see an interaction similar to what is shown in Figure 2.

Figure 2. A view from the Memcached console (click to enlarge)

Using Memcached for server responses

So far you've seen how to reduce the load on your database by using Memcached as a second-level cache in Hibernate. Not all application scenarios are quite so simple, however. For instance, how should you handle web pages that process and display data from a web service? You'll find your application using CPU-intensive logic to build markup for every response, which probably would be better off cached. If you cache the generated markup then the next request for that markup will return from the cache instead of going to a servlet.

The first step is to build a simple Servlet filter to intercept the request. Start by copying the CachingResponseWrapper.java and CachingResponseWriter.java into a com.javaworld.Memcached.filter package. Together these two classes will collect the responses generated by ContactServlet into a String.

Next, create a CacheFilter.java in the com.javaworld.Memcached.filter package and change its doFilter() method so that it looks like this:

Listing 4. CacheFilter.java

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.debug("Inside CachingFilter.doFilter() " );
        try {
            HttpServletRequest httpServletRequest = (HttpServletRequest)request;
            HttpServletResponse httpServletResponse = (HttpServletResponse)response;
            ObjectPool<MemcachedClient> MemcachedClientPool = MemcachedHelper.getMemcachedConnectionPool();
            MemcachedClient  MemcachedClient = MemcachedClientPool.borrowObject();
            StringBuffer cacheKeyBuffer = new StringBuffer();
            cacheKeyBuffer.append(httpServletRequest.getContextPath());
            cacheKeyBuffer.append(httpServletRequest.getServletPath());
            if(httpServletRequest.getQueryString() != null){
                cacheKeyBuffer.append("?");
                cacheKeyBuffer.append(httpServletRequest.getQueryString());
            }
            
            String cacheKey = httpServletResponse.encodeURL(cacheKeyBuffer.toString());
            System.out.println ("Get Path Info  " + cacheKey);
            String cachedResponse =(String) MemcachedClient.get(cacheKey);
            
            if( cachedResponse == null){
                System.out.println("Response is not cached forwarding control to servlet");
                CachingResponseWrapper cachingResponseWrapper =new CachingResponseWrapper((HttpServletResponse)response);
                chain.doFilter(request, cachingResponseWrapper);
                CachingResponseWriter collectResponseWriter = (CachingResponseWriter)cachingResponseWrapper.getWriter();
                String collectedResponseStr = collectResponseWriter.getCollectedResponse();//.replaceAll("\n", "") ;
                System.out.println( "Set value in the Memcached for key " + httpServletResponse.encodeURL(collectedResponseStr));
                
                System.out.println("Result of set" + MemcachedClient.set(cacheKey, 0, collectedResponseStr).get());
                //MemcachedClient.flush().get();
            }else{
                System.out.println("Returning cached response ");
                response.setContentType("text/html");
                response.getWriter().println(cachedResponse);
            }
            //MemcachedClient.flush().get();
            MemcachedClientPool.returnObject(MemcachedClient);
        } catch (NoSuchElementException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Note that in the doFilter() method you're first building the URL that the client used to access the ContactServlet; this URL is used as a key for caching. Once the URL is built, you'll execute MemcachedClient.get(cacheKey) to check if the response for the given URL is already cached. If it is, then return it; if it isn't, then wrap the response object into a CachingResponseWriter and pass control to a servlet that will generate the necessary markup. After the control returns from the servlet, call a collectResponseWriter.getCollectedResponse() method. This method will return the response generated by the servlet as a String. Take that response and save it in your cache with its URL as the key. The next time this query is used, the response will come from your cache and not from the servlet.

Next you'll need to change the web.xml for the ManageContact web application in order to intercept requests going to ContactServlet and cache them. You do this by declaring the CachingFilter and applying it to ContactServlet, as shown in Listing 5.

Listing 5. A CachingFilter for ContactServlet

<web-app>
    <display-name>Archetype Created Web Application</display-name>
 <filter>
        <filter-name>CachingFilter</filter-name>
        <display-name>CachingFilter</display-name>
        <filter-class>com.javaworld.Memcached.filter.CachingFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CachingFilter</filter-name>
        <servlet-name>ContactServlet</servlet-name>
    </filter-mapping> 
    <servlet>
        <servlet-name>ContactServlet</servlet-name>
        <servlet-class>com.javaworld.Memcached.ContactServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ContactServlet</servlet-name>
        <url-pattern>/contact</url-pattern>
    </servlet-mapping>
</web-app>
Figure 3. The Memcached console displays a caching request (click to enlarge)

This caching solution works to reduce high CPU usage on the application sever. It can be used to cache a full-page response or a fragment of a page. An advantage of this approach is that you can read the response from any other client and skip going to the application server altogether.

In conclusion

If you have a web application whose traffic is increasing exponentially, then you have the best problem in the world -- but it still could be a challenge to make the application scale. One way to ensure that performance stays on track is to start caching more data. But traditional data caching steals heap space, which can affect application performance another way.

Memcached works around the problems of traditional caching by caching the data in separate process from application memory. It also lets you cache more data by distributing the cache across different machines. In this article, you've learned how to integrate Memcached into a traditional enterprise architecture. We've practiced setting up spymemcached, configuring Memcached as a caching implementation for Hibernate, and using Memcached to cache server responses. See the Resources section to learn more about using Memcached for Java applications.

Sunil Patil is a Java EE Architect working for Avnet Technology in San Francisco, California. He is the author of Java Portlets 101 (SourceBeat, April 2007) and has written numerous articles published by JavaWorld, IBM developerWorks, and O'Reilly Media. In addition to being an IBM Certified WebSphere Portal Server Application Developer and Administer, he is a Sun Microsystems Certified Java Programmer, a Web component developer, and a business component developer. You can view Sunil's blog at http://www.webspherenotes.com.

Learn more about this topic

This story, "Use Memcached for Java enterprise performance, Part 2: Database-driven web apps" was originally published by JavaWorld.

Copyright © 2012 IDG Communications, Inc.

1 2 Page 2
Page 2 of 2