To get the 2nd level cache working you need to do 2 things:
1 Cache Strategy. Enable a cache strategy for your Hibernate entity - either in the class with an annotation or in the hibernate mapping xml file if you are stuck with pre java5. This can be done for an entity by providing this little snippet into your hbm.xml file (a better place is to store the cache setting strategy in hibernate.cg.xml file )
<class name="org.grouter.domain.entities.Router" table="ROUTER">
<cache usage="transactional|read-write|nonstrict-read-write|read-only" />
<id ...
</class>
or using an annotation for your entity (if you are on java5 or greater)
@Entity
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) public class Router { ... }
And as mentioned above if you want to cache collections of an entity you need to specify caching on collection level:
<class name="org.grouter.domain.entities.Router" table="ROUTER">
<cache usage="transactional|read-write|nonstrict-read-write|read-only"/>
<id ...
<set name="nodes">
<cache usage="transactional|read-write|nonstrict-read-write|read-only"/>
...
</set>
</class>
Page 106 of 112
Hibernate has something called a cache region which by default will be the full qualified name of your Java class. And if you like me are a fan of convention over configuration you will use the default region for an entity. A cache region will also be needed for the collection using the full qualified name of the Java class plus the name of the collection name (i.e. org.grouter.domain.entities.Router.nodes)
2 Cache provider. Setting up the physical caching for a cache provider. If you are using EHCache - which is the most common choice i dear to say - then you will need to specify some settings for the cache regions of your entities in a file called ehcache.xml. The EHCache will look for this file in the classpath and if not found it will fallback to ehcache-failsafe.xml which resides in the ehcache.jar library A typical sample for an EHCache configuration could look like (see mind map below for explanations):
<cache name="org.grouter.domain.entities.Router" maxElementsInMemory="1000" eternal="false"
timeToLiveSeconds="600" overflowToDisk="false"/>
and
<cache name="org.grouter.domain.entities.Router.nodes" maxElementsInMemory="1000" eternal="false"
timeToLiveSeconds="600" overflowToDisk="false"/>
The name maps to the name of the cache region of your entity. The attribute maxelementsInMemory needs to be set so that Hibernate does not have to swap in and out elements from the cache. A good choice for a read only cache would be as many entities there are in the database table the entity represents. The attribute eternal, if set to true means that any time outs specified will be ignored and entities put into the cache from Hibernate will live for ever.
Below is a mindmap for the second level cache and how it relates to the SessionFactory and the 1st level cache.
Page 107 of 112
Page 108 of 112
The Query cache
The Query cache of Hibernate is not on by default. It uses two cache regions called
org.hibernate.cache.StandardQueryCache and org.hibernate.cache.UpdateTimestampsCache. The first one stores the query along with the parameters to the query as a key into the cache and the last one keeps track of stale query results. If an entity part of a cached query is updated the the query cache evicts the query and its cached result from the query cache. Of course to utilize the Query cache the returned and used entities must be set using a cache strategy as discussed previously. A simple load( id ) will not use the query cache but instead if you have a query like:
Query query = session.createQuery("from Router as r where r.created = :creationDate");
query.setParameter("creationDate", new Date());
query.setCacheable(true);
List l = query.list(); // will return one instance with id 4321
Hibernate will cache using as key the query and the parameters the value of the if of the entity.
{ query,{parameters}} ---> {id of cached entity}
{"from Router as r where r.id= :id and r.created = :creationDate", [ new Date() ] } ----> [ 4321 ] ]
Page 109 of 112
Pragmatic approach to the 2nd level cache
How do you now if you are hitting the cache or not? One way is using Hibernates SessionFactory to get statistics for cache hits. In your SessionFactory configuration you can enable the cache statistics by:
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.use_sql_comments">true</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.generate_statistics">true</prop>
<prop key="hibernate.cache.use_structured_entries">true</prop>
The you might want to write a unit test to verify that you indeed are hitting the cache. Below is some sample code where the unit test is extending Springs excellent
AbstractTransactionalDataSourceSpringContextTests
public class MessageDAOTest extends AbstractDAOTests // which extends AbstractTransactionalDataSourceSpringContextTests
{
public void testCache() {
long numberOfMessages = jdbcTemplate.queryForInt("SELECT count(*) FROM message
");
System.out.println("Number of rows :" + numberOfMessages);
final String cacheRegion = Message.class.getCanonicalName();
SecondLevelCacheStatistics settingsStatistics = sessionFactory.getStatistics().
getSecondLevelCacheStatistics(cacheRegion);
StopWatch stopWatch = new StopWatch();
for (int i = 0; i < 10; i++) {
stopWatch.start();
messageDAO.findAllMessages();
stopWatch.stop();
System.out.println("Query time : " + stopWatch.getTime());
assertEquals(0, settingsStatistics.getMissCount());
assertEquals(numberOfMessages * i, settingsStatistics.getHitCount());
stopWatch.reset();
System.out.println(settingsStatistics);
endTransaction();
// spring creates a transaction when test starts - so we first end it then start a new in the loop
startNewTransaction();
} } }
Page 110 of 112
The output could look something like:
30 Jan 08 23:37:14 INFO org.springframework.test.AbstractTransactionalSpringContextTests:323 - Began transaction (1): transaction manager
[org.springframework.orm.hibernate3.HibernateTransactionManager@ced32d]; default rollback = true
Number of rows :6 Query time : 562
SecondLevelCacheStatistics[hitCount=0,missCount=0,putCount=6,elementCountInMemory=6,elem entCountOnDisk=0,sizeInMemory=8814]
30 Jan 08 23:37:15 INFO org.springframework.test.AbstractTransactionalSpringContextTests:290 - Rolled back transaction
after test execution
30 Jan 08 23:37:15 INFO org.springframework.test.AbstractTransactionalSpringContextTests:323 - Began transaction (2):
transaction manager
[org.springframework.orm.hibernate3.HibernateTransactionManager@ced32d]; default rollback = true
Query time : 8
SecondLevelCacheStatistics[hitCount=6,missCount=0,putCount=6,elementCountInMemory=6,elem entCountOnDisk=0,sizeInMemory=8814]
30 Jan 08 23:37:15 INFO org.springframework.test.AbstractTransactionalSpringContextTests:290 - Rolled back transaction
after test execution
30 Jan 08 23:37:15 INFO org.springframework.test.AbstractTransactionalSpringContextTests:323 - Began transaction (3):
transaction manager
[org.springframework.orm.hibernate3.HibernateTransactionManager@ced32d]; default rollback = true
Query time : 11