Understanding Caching in Hibernate – Part Three : The Second Level Cache

In the last posts I already covered the session cache as well as the query cache. In this post I will focus on the second-level cache. The Hibernate Documentation provides a good entry point reading on the second-level cache.

The key characteristic of the second-level cache is that is is used across sessions, which also differentiates it from the session cache, which only – as the name says – has session scope. Hibernate provides a flexible concept to exchange cache providers for the second-level cache. By default Ehcache is used as caching provider. However more sophisticated caching implementation can be used like the distributed JBoss Cache or Oracle Coherence.

First we have to modify our code sample so that we now load the Person object in two sessions. The source code then looks as follows

public void loadInTwoSessions (){
   // loading in first session
   Session session = getSessionFactory().openSession();
   Transaction tx = session.beginTransaction();
   Person p = (Person) session.load(Person.class, 1L);
   System.out.println(p.getFirstName());
   tx.commit();
   session.close();
   // loading in second session
   session = getSessionFactory().openSession();
   tx = session.beginTransaction();
   p = (Person) session.load(Person.class, 1L);   
   System.out.println(p.getFirstName());       
   tx.commit();
   session.close();       
}

As we have not activated the second level cache, we expect the SQL queries to be executed twice. Looking at the PurePath of this transactions verifies our asumption.

Loading a person object in two sessions without second-level cache

Loading a person object in two sessions without second-level cache

Now we activate the second-level cache. Activating the second level cache requires us change to Hibernate configuration file and enable second-level caching by adding and additionally specify the cache provider as shown below.


<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

In this example I am using Ehcache for demonstration purposes. In order to enable caching of our Person objects if have to specify the caching configuration in the ehcache.xml file.  The actual cache configuration depends on the caching provider. For Ehcache the configuartion is defined as follows. The configuration for the Person class used in the example is boiler-plate Ehcache configuration. It can be adopted to specific needs. Describing all possible configurations options like using mulitple cache regions etc. is beyond scope of this post.

<cache name="com.dynatrace.samples.database.Person"
   maxElementsInMemory="300"
   eternal="true"
   overflowToDisk="false"
   timeToIdleSeconds="12000"
   timeToLiveSeconds="12000"
   diskPersistent="false"
   diskExpiryThreadIntervalSeconds="120"
   memoryStoreEvictionPolicy="LRU"       
/>

Finally we have to configure caching also at Hibernate level. Hibernate supports mulitple settings for caching. As we are only reading data it the moment a read-only cache is sufficient for our purposes. Hibernate for sure supports  read-write cache as well and also transactional caches in case this is supported by the cache provider.  The following liine in the hibernate configuration enable read-only caching for Person objects. Alternatively also Hibernate associations could be used.

<cache usage=”read-only” />

Now we expect the object to be retrieved from the second-level the second time it is loaded. A PurePath trace verifies this assumption.  Now, only the first time a database call gets executed.

Loading in two sessions with enabled second-level cache

Loading a person object in two sessions with enabled second-level cache

Read-Write Caching

After having looked at plain read caching we look in the next step at read-write caching.  Our code example gets a bit more complex. We again use two sessions. We load the object in the first session, update it thenload it in the second session. Both sessions are created upfront and are kept open until the end.

public void complexLoad (){   
  Session session1 = getSessionFactory().openSession();
  Session session2 = getSessionFactory().openSession();        

  Transaction tx1 = session1.beginTransaction();
  Person p1 = (Person) session1.load(Person.class, 1L);
  System.out.println (p1.getFirstName());
  p1.setFirstName ("" + System.currentTimeMillis());
  tx1.commit();

  Transaction tx2 = session2.beginTransaction();
  Person p2 = (Person)session2.load(Person.class, 1L);
  System.out.println (p2.getFirstName());
  tx2.commit();

  session1.close();
  session2.close();
}

We expect the object to be retrieved from the cache when it is loaded in the second session.  Looking at the PurePath of this transaction however shows something different. This method executes three SQL statements. First a SELECT to load the Person object, then an UPDATE to update the record in the database and then again a SELECT to load the Person object for the second session.

Two transaction loading a person object both times leading to a database query.

Two transaction loading a person object both times leading to a database query.

This is not what we necessarily where expecting. The object could have been retrieved from the cache in the second session. However it got loaded from the database. So why wasn’t the object taken from the cache. A closer look at the internal Hibernate behaviour unveils this secret.

Details on loading from second-level cache

Details on loading from second-level cache

The PurePath snippet above shows the details for loading the Person object in the second session. The key is the isGettable method, which in this case returns false. The input to isGettable is the session creation timestamp, as indicated by the arrow.  A look at the sourcecode unveils what is checked within this method.

public boolean isGettable(long txTimestamp) {
 return freshTimestamp < txTimestamp;
}

The method verifies wheter the session’s timestamp (txTimestamp) is greated than the freshTimestamp of the cached object. In our case the second session was created BEFORE the object was updated. Consequently this method will return false. If we modify our code as follows the object will be loaded from the second-level cache.

public void complexLoad (){   
  Session session1 = getSessionFactory().openSession();
  Transaction tx1 = session1.beginTransaction();
  Person p1 = (Person) session1.load(Person.class, 1L);
  System.out.println (p1.getFirstName());
  p1.setFirstName ("" + System.currentTimeMillis());
  tx1.commit();

  Session session2 = getSessionFactory().openSession();  
  Transaction tx2 = session2.beginTransaction();
  Person p2 = (Person)session2.load(Person.class, 1L);
  System.out.println (p2.getFirstName());
  tx2.commit();

  session1.close();
  session2.close();
}

The PurePath snipped below verfies this assumption and shows that this time isGettable returns true and the object is retrieved from the cache.

Loading the Person object from second-level cache directly

Loading the Person object from second-level cache directly

Interaction of Session and Second-Level Cache

Finally I want to take a short look of the interaction between the session and the second-level cache. The important point to understand is that as soon as we use the second-level cache we have two caches in place. Caches are always a source of inconsistent information, which we take as the price for better performance and scalability. In order to avoid problems and unwanted behaviour we have to understand their internal behaviour.  Hibernate always tries to first retrieve objects from the session and if this fails tries to retrieve them from the second-level cache. If this fails again objects are directly loaded from the database.  The PurePath snippet below shows this loading behavior.

Load hierarchy in Hibernate showing logical flow of object retrieval

Load hierarchy in Hibernate showing logical flow of object retrieval

Conclusion

The second level cache is powerful mechanism for improving performance and scalability of your database driven application. Read-only caches are easy to handle, while read-write caches are more subtile in their behavior. Especially the interaction with the Hibernate session can lead to unwanted behavior. Sessions should therefore be used as what they are designed for – a transactional context. There are more details on the second-level cache I did not elaborate on like synchronization or replication behavior. However the combination of the three caching articles should provide good insight into Hibernate caching behavior.

If you’re interested in getting a better idea of how dynaTrace provides this transactional context (PurePath), you can check out this 14 minute video I contributed to which gets into some of the details of the product.

转自:http://apmblog.compuware.com/2009/03/24/understanding-caching-in-hibernate-part-three-the-second-level-cache/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值