NHibernate cache system. Part 2

In the previous post we saw how the cache system is structured in NHibernate and how it works. We saw that we have different methods to play with the cache (Evict, Clear, Flush …) and they are all associated with the ISession object because the cache of level 1 is associated with the lifecycle of an ISession object.

In this second article we will see how the second level cache works and how it is associated with the ISessionFactory object that is in charge of controlling this cache mechanism.

Second Level cache architecture

How does the second level cache work?

First of all, when an entity is cached in the second level cache, the entity is disassembled into a collection of keys/values pair, like a dictionary and persisted in the cache repository. This mechanism is accomplished because most of the second level cache providers are able to persist serialized dictionary collections and because in the same time NHibernate does not force you to make serializable your entities (something that IMHO, should never be done!!).

A second mechanism happens when we cache the result of a query (Linq, HQL, ICriteria) because these results can’t be cached using the first level cache (see previous blog post). After we cache a query result, NHibernate will cache only the unique identifiers of the entities involved in the result of the query.

Third, NHibernate has an internal mechanism that allows him to know and keep track of a timestamp value used to write tables or to work with sessions. How does it work? Well the mechanism is pretty clear, it keeps track of when the last table was written too. A series of mechanism will update this timestamp information and you can find a better explanation of Ayende’s blog: http://ayende.com/blog/3112/nhibernate-and-the-second-level-cache-tips.

Configuration of the second level cache

By default the second level cache is disabled. If you need to use the second level cache you have to let NHibernate know about that. The hibernate.cfg file has a dedicated section of parameters that should be used to enable the second level cache:

<propertyname="cache.provider_class">

NHibernate.Cache.HashtableCacheProvider

</property>

<!--Youhavetoexplicitlyenablethesecondlevelcache->

<propertyname="cache.use_second_level_cache">

true

</property>

First of all we specify the cache provider we are using, in this case I am using the standard hashtable provider, but I will show you in the next article what are the real providers you should use. Second we say that the cache should be enabled; this part is really important because if you do not specify that the cache is enable, it simply won’t work …

Then you may provide to the cache a default expiration in seconds:

<!-- cache will expire in 2 minutes -->

<propertyname="cache.default_expiration">120</property>

If you want to add additional configuration properties, they will be cache provider specific!

Cache by mapping

One of the possible configuration is to enable the cache at the entity level. This means that we are marking our entity as “cachable”.

<class

name="CachableProduct“

table="[CachableProduct]“

dynamic-insert="true“

dynamic-update="true">

<cacheusage="read-write"/>

In order to do that we have to introduce a new tag, the <cache> tag. In this tag we can specify different type of “”usage”:

Read-write

It should be used if you plan also to update the data (no with serializable transaction)

Read-only

Simplest and best performing, for read only access

Nonstrict-read-write

If you need to occasionally update the data. You must commit the transaction

Transactional

not documented/implemented yet because no one cache provider allows transactional cache. It is implemented in the Java version because J2EE allow transactional second level cache

Now, if we write a simple test that will create some entities and will try to retrieve them using two different ISession generated by the same ISessionFactory we will get the following behavior:

As you can see the second session will access the 2nd level cache using a transaction and will not use the database at all. This has been accomplished just by mapping the entity with the <cache> tag and by using the GET<T> method.

Let’s make everything a little bit more complex. Let’s assume for a second that our object is an aggregate root and it is more complex than the previous one. If we want to cache also a collection of child or a parent reference we will need to change our mapping in the following way:

<!-- inside the product mapping file -->

<bagname="Attributes"cascade="all"inverse="true">

<cacheusage="read-write"/>

<keycolumn="ProductId"/>

<one-to-manyclass="CacheAttribute"/>

</bag>

<!-- inside theCacheAttribute file -->

<class

name="CacheAttribute"

table="[CacheAttribute]"

dynamic-insert="true"

dynamic-update="true">

<cacheusage="read-write"/>

<!-- omit -->

<many-to-one

class="CachableProduct"

name="Product"cascade="all">

<columnname="ProductId"/>

</many-to-one>

</class>

Now we can execute the following test (I am omitting some parts for saving space, I hope you don’t mind …)

In this case the second ISession is calling the cache 4 times in order to resolve all the objects (2 products x 2 categories).

Cache a query result

Another way to cache our result is by creating a cachable query that is slightly different than creating a cachable object.

Important note:

In order to cache a query we need to set the query as “cachable” and then set the corresponding entity as “cachable” too. Otherwise NHB will cache the ID of the entity but then it will always fetch the entity and cache only the query result.

To write a cachable query we need to implement an IQuery object in the following way:

In this case the cache is telling us that the second session has 1 query result cached and that we called it once.

Final advice

As you saw using the 1st and 2nd level cache is a pretty straightforward process but it requires time and understanding of NHibernate cache mechanism. Below are some final advice that you should keep in consideration when working with the 2nd level cache:

2nd Level Cache is never aware of external database changes!

Default cache system is hashtable, you must use a different one

Wrong implementation of the 2nd level cache may result in a non expected performance degrade (i.e. hashtable doc)

First level cache is shared across same ISession, second level is shared across same ISessionFactory

In the next article we will see what are the available cache providers.

Disclaimer

All content provided on this blog is for informational purposes only. The owner of this blog makes no representations as to the accuracy or completeness of any information on this site or found by following any link on this site.

Advise for SpammersThis blog is manually moderated, please stop to post scam, spam and other annoying comments as they will never get approved, thank you.

Support MeDid you enjoy the content of this blog, then just click the banner below, it will give me back some money which I will use to keep running this blog.