Hibernate Caching

Teera.K@sun.com, sang.shin@sun.comwww.javapassion.com/j2ee


In this hands-on lab, you are going to learn how to configure and enable Hibernate's second-level caching capability.  This lab is created based on Speed up your Hibernate Applications with Second-Level Caching article from www.devx.com.


Expected duration: 65 minutes

Software Needed

Before you begin, you need to install the following software on your computer. The Hibernate library files  are already included as part of the hands-on lab zip file so you don't have to download Hibernate yourself.


Change Log


Lab Exercises


Exercise 0: Start Java DB (code-named as Derby) database server and create "mydatabase" database

    1. Start the Java DB (Derby) database server if you have not done so yet. 
    2. Create "mydatabase" database if you have not done so yet. 
3. Create NetBeans library called HibernateCore as described here if you have not done so yet.
    4. Run create.sql SQL script which is located under <LAB_UNZIPPED_DIRECTORY>/hibernatecaching/script directory. This script will create database table used in this lab.



    5. Repeat the above step for the folloing scripts in the order specified. These scripts are located under  <LAB_UNZIPPED_DIRECTORY>/hibernatecaching/script directory.

Exercise 1: Hibernate Query Without Caching

In this exercise, you are going to run Hibernate querying on large database without caching and examine query result.

(1.1) Open and run "HibernateCache" project

1. Open HibernateCache NetBeans project. 


2. Resolve the references to Hibernate library files and client JDBC driver as desribed in step 1.1 of the "Hibernate Basics" hands-on lab.

3. Build HibernateCache project.


4. Run the HibernateCache Test Package




Figure 1.1-1 Result from running CountryDAOTest.java will look like


 

(1.2) Look under the hood of the "HibernateCache" project

1. Study Country.java code, as shown in figure 1.2-1 below. This is a POJO class.

package com.wakaleo.articles.caching.businessobjects;

import java.util.Set;

public class Country {
   
    private Long id;
    private String code;
    private String name;
    private Set airports;
   
    public String getCode() {
        return code;
    }
   
    public void setCode(String code) {
        this.code = code;
    }
   
    public Long getId() {
        return id;
    }
   
    public void setId(Long id) {
        this.id = id;
    }
   
    public String getName() {
        return name;
    }
   
    public void setName(String name) {
        this.name = name;
    }
   
   
    public Set<Airport> getAirports() {
        return airports;
    }
   
    public void setAirports(Set<Airport> airports) {
       
        this.airports = airports;
    }
   
   
}
Country.java

2. Study Country.hbm.xml, this is Hibernate entity mapping file for Country. Pay attention to the bold-fonted part indicating that hibernate caching is disabled.

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
    <class name="Country" table="COUNTRY" dynamic-update="true">
        <meta attribute="implement-equals">true</meta>
        <!--cache usage="read-only"/-->

        <id name="id" type="long" unsaved-value="null" >
            <column name="cn_id" not-null="true"/>
            <generator class="increment"/>
        </id>
       
        <property column="cn_code" name="code" type="string"/>
        <property column="cn_name" name="name" type="string"/>
       
        <set name="airports" >
            <!-- cache usage="read-only"/ -->
            <key column="cn_id"/>
            <one-to-many class="Airport"/>
        </set>
    </class>
</hibernate-mapping>
Country.hbm.xml

3. Study CountryDAO.java code as shown in figure 1.2-3 below. This is Data Access Object class that is used to retrieve Country entity from database.

package com.wakaleo.articles.caching.dao;

import java.util.List;
import com.wakaleo.articles.caching.businessobjects.Country;

public class CountryDAO {
   
    public Country findCountryByCode(String code) {
        return (Country) SessionManager.currentSession()
        .createQuery("from Country as c where c.code = :code")
        .setParameter("code",code)
        .uniqueResult();
    }
   
    public List getCountries() {
        return SessionManager.currentSession().createQuery("from Country as c order by c.name").setCacheable(true).list();
    }
}
CountryDAO.java

4. Study SessionManager.java class as shown in figure 1.2-4 below. This class manages Hibernate session and logging.

package com.wakaleo.articles.caching.dao;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public final class SessionManager {
   
    /**
     * Parameter used to define the XML Hibernate configuration file
     */
    private static final String HIBERNATE_CONFIGURATION_FILE_PROPERTY = "hibernate.configuration.file";
   
    /**
     * Class logger
     */
    private static Log log = LogFactory.getLog(SessionManager.class);
   
    /**
     * The one and only session manager instance
     */
    private static SessionManager sessionManager = new SessionManager();
    /**
     * The one and only Hibernate session factory.
     */
    private static final SessionFactory sessionFactory;
    static {
        try {
// Create the SessionFactory
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (HibernateException ex) {
            throw new RuntimeException("Configuration problem: " + ex.getMessage(), ex);
        }
    }
   
    /**
     * The thread-local Hibernate session object
     */
    private static final ThreadLocal threadSession = new ThreadLocal();
   
    public static Session getSession() throws HibernateException {
        return currentSession();
    }
   
    public static Session currentSession() throws HibernateException {
        log.debug("Hibernate Session Manager :Fetching a hibernate session");
        Session s = (Session) threadSession.get();
// Open a new Session, if this Thread has none yet
        if (s == null) {
            s = sessionFactory.openSession();
            threadSession.set(s);
        }
        if (!s.isOpen()) {
            s = null;
            s = sessionFactory.openSession();
            threadSession.set(s);
        }
       
        if (!s.isConnected()) {
            s.reconnect();
        }
        log.debug("Hibernate Session Manager :Fetching a hibernate session done.");
        return s;
    }
   
    /**
     * Close the current Hibernate session
     * @throws HibernateException
     * @throws InfrastructureException
     */
    public static void closeSession() throws HibernateException {
        log.debug("Hibernate Session Manager : closeSession");
        Session s = (Session) threadSession.get();
        threadSession.set(null);
        if (s != null && s.isOpen()) {
            log.debug("Hibernate Session Manager : closing session");
            s.close();
            log.debug("Hibernate Session Manager : session closed");
        }
        s = null;
    }
   
    /**
     * Connect this session to the current thread
     * @param session
     * @throws HibernateException
     */
    public static void reconnect(Session session) throws HibernateException {
        if (!session.isConnected()) {
            session.reconnect();
        }
        threadSession.set(session);
    }
   
    /**
     * Disconnect the current session from the current thread
     * @return
     * @throws HibernateException
     * @throws HibernateException
     */
    public static Session disconnectSession() throws HibernateException {
        Session session = currentSession();
        if (session.isConnected() && session.isOpen()) {
            session.disconnect();
        }
        return session;
    }
   
    /**
     * Default constructor
     */
    private SessionManager() {
    }
   
    /**
     * Returns an instance of a session mananger used to obtain
     * a Hibernate Session object.
     * @return the Hibernate session manager.
     */
    public static SessionManager getInstance() {
        return sessionManager;
    }
   
    public static Session openSession() throws HibernateException {
        return SessionManager.currentSession();
    }
}
SessionManager.java

5. Study Hibernate.cfg.xml, this is Hibernate configuration file. Pay attention to the bold-fonted part indicating that query and secon-level caching are turned off

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">org.apache.derby.jdbc.ClientDriver</property>
        <property name="connection.url">jdbc:derby://localhost:1527/mydatabase</property>
        <property name="connection.username">app</property>
        <property name="connection.password">app</property>
       
        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <!-- <property name="hibernate.hbm2ddl.auto">create</property>-->
        <property name="hibernate.show_sql">false</property>
        <!-- <property name="hibernate.connection.autocommit">true</property> -->
        <!--<property name="hibernate.format_sql">true</property>-->
        <property name="transaction.factory_class">
            org.hibernate.transaction.JDBCTransactionFactory
        </property>
       
        <property name="hibernate.cache.use_query_cache">false</property>
        <property name="hibernate.cache.use_second_level_cache">false</property>
        <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
       
        <mapping resource="com/wakaleo/articles/caching/businessobjects/Country.hbm.xml"/>
        <mapping resource="com/wakaleo/articles/caching/businessobjects/Employee.hbm.xml"/>
        <mapping resource="com/wakaleo/articles/caching/businessobjects/Language.hbm.xml"/>
        <mapping resource="com/wakaleo/articles/caching/businessobjects/Airport.hbm.xml"/>
    </session-factory>
</hibernate-configuration>
Hibernate.cfg.xml

6. Study the test package: TestTimer.java, CountryDAOTest.java, and EmployeeDAOTest.java.  TestTiner.java is used to record the Hibernate query execution time.

package com.wakaleo.articles;

import junit.framework.TestCase;

/**
 * Simple utilities class for benchmarking unit tests. Use is a follows:
 *     TestTimer t = new TestTimer("My task");
 *        // Do something
 *     t.done();
 *
 * @author jfsmart
 */
public class TestTimer extends TestCase {
   
    private long startTime;
    private String message;
   
    /**
     * Initiate a timer
     */
    public TestTimer(String message) {
        startTime = System.currentTimeMillis();
        this.message = message;
    }
   
    /**
     * Reset the timer for another timing session.
     *
     */
    public void reset() {
        startTime = System.currentTimeMillis();
    }
   
    /**
     * End the timing session and output the results.
     */
    public void done() {
       
        System.out.println(message
                + " : "
                + (System.currentTimeMillis() - startTime)
                + " ms.");
    }
}
TestTimer.java

7. Study the CountryDAOTest.java.  It is used to test CountryDAO.java. Pay attention to bold-fonted part that create Hibernate session and query database for list of country entities.

package com.wakaleo.articles.caching.dao;

import java.util.List;

import org.hibernate.Transaction;

import com.wakaleo.articles.TestTimer;
import com.wakaleo.articles.caching.businessobjects.Country;

import junit.framework.TestCase;

public class CountryDAOTest extends TestCase {
   
        /*
         * Test method for 'com.wakaleo.articles.caching.dao.CountryDAO.getCountries()'
         */
    public void testGetCountries() {
        CountryDAO dao = new CountryDAO();
        for(int i = 1; i <= 5; i++) {
            Transaction tx = SessionManager.getSession().beginTransaction();
            TestTimer timer = new TestTimer("testGetCountries");
            List countries = dao.getCountries();
            tx.commit();
            timer.done();
            SessionManager.closeSession();
            assertNotNull(countries);
        }
    }
}
CountryDAOTest.java

8. Study the EmployeeDAOTest.  This class is used to test EmployeeDAO.java. Pay attention to bold-fonted part that create Hibernate session and query database.

package com.wakaleo.articles.caching.dao;

import java.util.List;

import org.hibernate.Transaction;

import com.wakaleo.articles.TestTimer;
import com.wakaleo.articles.caching.businessobjects.Country;

import junit.framework.TestCase;

public class EmployeeDAOTest extends TestCase {
   
    CountryDAO countryDao = new CountryDAO();
    EmployeeDAO employeeDao = new EmployeeDAO();
   
    /**
     * Ensure that the Hibernate session is availiable
     * to avoid the Hibernate initialisation interfering with
     * the benchmarks
     */
    protected void setUp() throws Exception {
        super.setUp();
        SessionManager.getSession();
    }
   
    public void testGetNZEmployees() {
        TestTimer timer = new TestTimer("testGetNZEmployees");
        Transaction tx = SessionManager.getSession().beginTransaction();
        Country nz = countryDao.findCountryByCode("nz");
        List kiwis = employeeDao.getEmployeesByCountry(nz);
        tx.commit();
        SessionManager.closeSession();
        timer.done();
    }
   
    public void testGetAUEmployees() {
        TestTimer timer = new TestTimer("testGetAUEmployees");
        Transaction tx = SessionManager.getSession().beginTransaction();
        Country au = countryDao.findCountryByCode("au");
        List aussis = employeeDao.getEmployeesByCountry(au);
        tx.commit();
        SessionManager.closeSession();
        timer.done();
    }
   
    public void testRepeatedGetEmployees() {
        testGetNZEmployees();
        testGetAUEmployees();
        testGetNZEmployees();
        testGetAUEmployees();
    }
}
EmployeeDAOTest.java

                return to top of the exercise

Summary

In this exercise,  you have learned how to run Hibernate query and measure the execution time

return to the top

Exercise 2: Hibernate Query With Caching Enabled

In this exercise, you are going to turn on Hibernate caching, query database, and compare performance result with previous runs.

 

(2.1) Change Hibernate configuration to allow caching

1. Open Country.hbm.xml file and uncomment cache-usage configuration (the bold-fonted part)

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
    <class name="Country" table="COUNTRY" dynamic-update="true">
        <meta attribute="implement-equals">true</meta>
        <cache usage="read-only">

        <id name="id" type="long" unsaved-value="null" >
            <column name="cn_id" not-null="true"/>
            <generator class="increment"/>
        </id>

        <property column="cn_code" name="code" type="string"/>
        <property column="cn_name" name="name" type="string"/>

        <set name="airports" >
            <cache usage="read-only">
            <key column="cn_id"/>
            <one-to-many class="Airport"/>
        </set>
    </class>
</hibernate-mapping>
Country.hbm.xml file

2. Repeat step 1 on Airport.hbm.xml, Employee.hbm.xml, Language.hbm.xml to enable Hibernate caching on all entity.

3. Open hibernate.cfg.xml file to change Hibernate configuration. Change the value of hibernate caching property in the configuration file to true (font-bolded part). This will enable query caching and secon-level caching. 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">org.apache.derby.jdbc.ClientDriver</property>
        <property name="connection.url">jdbc:derby://localhost:1527/mydatabase</property>
        <property name="connection.username">app</property>
        <property name="connection.password">app</property>
       
        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <!-- <property name="hibernate.hbm2ddl.auto">create</property>-->
        <property name="hibernate.show_sql">false</property>
        <!-- <property name="hibernate.connection.autocommit">true</property> -->
        <!--<property name="hibernate.format_sql">true</property>-->
        <property name="transaction.factory_class">
            org.hibernate.transaction.JDBCTransactionFactory
        </property>
       
        <property name="hibernate.cache.use_query_cache">true</property>
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
       
        <mapping resource="com/wakaleo/articles/caching/businessobjects/Country.hbm.xml"/>
        <mapping resource="com/wakaleo/articles/caching/businessobjects/Employee.hbm.xml"/>
        <mapping resource="com/wakaleo/articles/caching/businessobjects/Language.hbm.xml"/>
        <mapping resource="com/wakaleo/articles/caching/businessobjects/Airport.hbm.xml"/>
    </session-factory>
</hibernate-configuration>
hibernate.cfg.xml

4. Build HibernateCache project.

5. Run the HibernateCache Test Package

[org.hibernate.cache.StandardQueryCache]; using defaults.
testGetCountries : 375 ms.
testGetCountries : 47 ms.
testGetCountries : 31 ms.
testGetCountries : 32 ms.
testGetCountries : 31 ms.



[org.hibernate.cache.StandardQueryCache]; using defaults.
testGetNZEmployees : 484 ms.
testGetAUEmployees : 172 ms.
testGetNZEmployees : 31 ms.
testGetAUEmployees : 16 ms.
testGetNZEmployees : 47 ms.
testGetAUEmployees : 15 ms.



As you can see, the result between Hibernate query with and without caching on are not much different in the first run. However, querying time is reduced dramatically in the subsequent runs. This is because querying results from the first run is cached.


(2.2) Look under the hood of the EhCache configuration file

1. Study ehcache.xml file, as shown in figure 2.2-1 below. This file is Hibernate caching configuration. You can control caching property from here.

<ehcache>
    <diskStore path="java.io.tmpdir"/>
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120"
    />
   
    <cache name="com.wakaleo.articles.caching.businessobjects.Country"
           maxElementsInMemory="300"
           eternal="true"
           overflowToDisk="false"
    />
   
    <cache name="com.wakaleo.articles.caching.businessobjects.Employee"
           maxElementsInMemory="5000"
           eternal="false"
           overflowToDisk="false"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
    />
   
    <cache name="com.wakaleo.articles.caching.businessobjects.Language"
           maxElementsInMemory="100"
           eternal="true"
           overflowToDisk="false"
    />
   
</ehcache>
ehcache.xml

return to top of the exercise

Summary

In this exercise,  you have learned how to turn on query and second-level caching in Hibernate

return to the top