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



5. Repeat the above step for the folloing scripts in the order
specified. These scripts are located under
<LAB_UNZIPPED_DIRECTORY>/hibernatecaching/script directory.
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




(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; } } |
| <?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> |
| 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(); } } |
| 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(); } } |
| <?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> |
| 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."); } } |
| 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); } } } |
| 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(); } } |
In this exercise, you have learned
how to run Hibernate query and measure the execution time
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> |
2. Repeat step 1 on Airport.hbm.xml, Employee.hbm.xml, Language.hbm.xml to enable Hibernate caching on all entity.
| <?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> |
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> |
In this exercise, you have learned
how to turn on query and second-level caching in Hibernate