Quickly Setting Up And Using NHibernate’s Second Level Cache
Posted by Davy Brion on February 9th, 2009
The purpose of this post is just to quickly go over what you need to do to get NHibernate’s 2nd Level Cache working in your application. If you want to read how the 1st and 2nd Level Caches work, please read Gabriel Schenker’s excellent and thorough post about it.
Anyways, the first thing you need to do, is to enable the 2nd level cache. Add the following 2 properties to your hibernate.cfg.xml file:
<property name="cache.use_second_level_cache">true</property>
<property name="cache.use_query_cache" >true</property>
The first one (obviously) enables the 2nd level cache, while the second one enables query caching. That basically means that you can (optionally) cache the results of specific queries. Note that this doesn’t mean that the results of all queries will be cached, only the ones where you specify that the results can be cached.
Next, you need to choose a CacheProvider. There are various options available, although i generally just use SysCache (which makes use of the ASP.NET Cache). You can download the CacheProviders here.
Once you’ve picked out a CacheProvider, you need to add a property for it to your hibernate.cfg.xml file as well:
<property name="cache.provider_class">NHibernate.Caches.SysCache.SysCacheProvider, NHibernate.Caches.SysCache</property>
Let’s first start with caching the results of a query. Suppose we have the following query:
var products = session.CreateCriteria(typeof(Product))
.Add(Restrictions.Eq("Category.Id", categoryId))
.List<Product>();
If we want NHibernate to cache the results of this query, we can make that happen like this:
var products = session.CreateCriteria(typeof(Product))
.SetCacheable(true)
.Add(Restrictions.Eq("Category.Id", categoryId))
.List<Product>();
When we execute this query, NHibernate will cache the results of this query. It is very important to know that it won’t actually cache all of the values of each row. Instead, when the results of queries are cached, only the identifiers of the returned rows are cached.
So what happens when we execute the query the first time with categoryId containing the value 1? It sends the correct SQL statement to the database, creates all of the entities, but it only stores the identifiers of those entities in the cache. The second time you execute this query with categoryId containing the value 1, it will retrieve the previously cached identifiers but then it will go to the database to fetch each row that corresponds with the cached identifiers.
Obviously, this is bad. What good is caching if it’s actually making us go to the database more often than without caching? That is where entity caching comes in. In this case, our query returns Product entities, but because the Product entity hasn’t been configured for caching, only the identifiers are cached. If we enable caching for Product entities, the resulting identifiers of the query will be cached, as well as the actual entities. In this case, the second time this query is executed with a categoryId with value 1, we won’t hit the database at all because both the resulting identifiers as well as the entities are stored in the cache.
To enable caching on the entity level, add the following property right below the class definition in the Product.hbm.xml file:
<class name="Product" table="Products">
<cache usage="read-write"/>
This tells NHibernate to store the data of Product entities in the 2nd level cache, and that any updates that we make to Product entities need to be synchronized in both the database and the cache.
That’s pretty much all you need to do to get the 2nd Level Cache working. But please keep in mind that there is a lot more to caching than what i showed in this post. Reading Gabriel’s post on caching is an absolute must IMO. Caching is a powerful feature, but with great power comes great responsibility. Learn how to use it wisely
February 20th, 2009 at 3:52 pm
Since one can use ASP.NET Cache outside web development, do you think SysCache will work in a WinForms app for example as well? I haven’t tried it yet, but it seems that it should. What do you thik would be the downfalls of such ussage?
February 20th, 2009 at 4:04 pm
it definitely works, although i’m not sure if it’a actually a good idea to do so… i don’t really think there’s a problem with it, but you might want to investigate possible downsides to using the ASP.NET cache outside of the ASP.NET environment.
February 21st, 2009 at 11:20 am
Hi,
The above post explains caching really well. But there is something that I am missing.
Following is the scenario which is failing. Can u guide me to the correct approach.
1. I populate the list using ICriteria. For example, I populate the list with student objects.
2. I have 4 students in the collection.
3. I edit the first object and change the ‘name’ property from Paul to Patrick.
4. I donot save or rollback.
4. I then need to undo the change, so I wish to re-populate the list from the database. Hence I call the List method of the ICriteria object once again.
5. This time I once again get 4 student objects, but the edited object’s ‘name’ property still shows me Patrick and not Paul.
How do I refresh the collection from the database?
I have added all the tags as sepcified in your post to enable the second level cache
Thanks in dvance.
-Hitesh.
February 21st, 2009 at 11:40 am
when you call the List method through Criteria again, are you still using the same session as you were using when you retrieved the objects the first time? If so, those modified entities are in your _first_ level cache (which is stored at the session level) and those are probably the results you’re getting back
If you are using the same session, try to .Clear the session before reloading the objects or .Evict the one you modified
February 21st, 2009 at 12:55 pm
Hi.
Thanks for the imediate response.
1. Yes, I am using the same session object the second time. I cannot use session.clear since the session object is being used for populating other objects too.
2. .Evict requires a single object as a parameter. However, there may be a situation where i make changes to more than one object (ex. i modify 3 objects in the list.) In such a case, I will need to loop through the list and evict each object one by one. Is there a way to avoid the loop.
3. I see that there is sessionFactory.EvictCollection method. This evicts all the lists created using that session Factory. However, there is overloaded sessionFactory.EvictCollection(rolename as string). But I am not able to figure out when and how to create a ‘role’.
Thanks again.
Hitesh.
February 21st, 2009 at 1:37 pm
not sure about the role thing either, haven’t used it yet
are you sure the lifetime of your session isn’t too long? If you have to load a list of objects, possibly modifying them, then having to discard your changes and having to reload them all within the same session, then you might be doing too much here.
An ISession is like a Unit Of Work… you should try to keep those pretty short.
May 5th, 2009 at 2:57 pm
Nice post. i would like to mention here that NCache is out with its new version with much better support for NHibernate Cache. It can boost NHibernate performance and scalibility.
December 28th, 2009 at 7:53 am
Hello Davy,
i’m am new to nhibernate and currently learning about the 2nd level cache..
i have a question.
what if the data is deleted or modified directly to the database table(not the cache)?
using sql update or delete statements of course.
can it still synchronize the data correctly?
Thanks,
Reggie
December 28th, 2009 at 9:33 am
@Reggie
the SysCache2 provider is supposed to support SQL Dependencies but i’ve never tried it.
generally speaking though, deleting/updating date directly in a production database is a _bad_ idea and will of course negatively impact pretty much any kind of caching