Caching frequently used, rarely updated data can greatly improve the performance of websites and other high traffic applications. In this recipe, we'll configure NHibernate's cache, just as we would for a typical public facing website.
NHibernate.Caches.SysCache.dll
to your solution's Lib
folder.NHibernate.Caches.SysCache.dll
.App.config
file.configSections
element, declare a section for the cache configuration:<section name="syscache" type="NHibernate.Caches.SysCache.SysCacheSectionHandler, NHibernate.Caches.SysCache" />
hibernate-configuration
section.<property name="cache.provider_class"> NHibernate.Caches.SysCache.SysCacheProvider, NHibernate.Caches.SysCache </property> <property name="cache.use_second_level_cache"> true </property> <property name="cache.use_query_cache"> true </property>
<class-cache class="Eg.Core.Product, Eg.Core" region="hourly" usage="read-only"/> <class-cache class="Eg.Core.ActorRole, Eg.Core" region="hourly" usage="read-only"/> <collection-cache collection="Eg.Core.Movie.Actors" region="hourly" usage="read-only "/>
hibernate configuration
section, add the syscache
section declared in the first step:<syscache> <cache region="hourly" expiration="60" priority="3" /> </syscache>
The cache.provider_class
configuration property defines the cache provider to use. In this case, we're using syscache
,
NHibernate's wrapper for ASP.NET's System.Web.Caching.Cache
.
The cache.use_second_level_cache
enables the second-level cache. If the second-level cache is enabled, setting cache.use_query_cache
will allow query results to be cached.
Caching must still be set up on a per-class hierarchy, per-collection, and per-query basis. That is, you must also set up caching for each specific item to be cached. In this recipe, we've set up caching for the Product
entity class, which, because they're in the same class hierarchy, implicitly sets up caching for Book
and Movie
with the same settings. In addition, we've set up caching for our ActorRole
entity class. Finally, because caching for collections is configured separately from entities, we set up caching for the Movie
's Actors
collection.
We've set up each of these to use a region of the cache named hourly
. A cache region separates the cached data and defines a set of rules governing when that data will expire. In this case, our hourly region is set to remove an item from the cache after 60 minutes or under stress, such as low memory. priority
can be set to a value from 1 to 5, with 1 being the lowest priority, and thus the first to be removed from the cache.
The cache concurrency strategy for each item, set with the usage
attribute, defines how an object's cache entry may be updated. In this recipe, we've set all of our product data to read-only
. Our public-facing website only displays our products. It doesn't change them. In other scenarios, it may be appropriate to use read-write
or, when concurrency isn't a concern, nonstrict-read-write
.
Caching is only meant to improve the performance of a properly designed NHibernate application. Your application shouldn't depend on the cache to function properly. Before adding caching, you should correct poorly performing queries and issues like SELECT N+1. This will usually give a significant performance boost, eliminating the need for caching and its added complexity.
NHibernate allows us to configure a cache with the same scope as the session factory. Logically, this cache is divided into three parts.
The entity cache stores persistent objects or the values of persistent objects. These objects are stored as a dictionary of POIDs to arrays of values, as shown in the following diagram:
Notice in the previous image that the Movie.Actors collection has a cache entry of its own. Also notice that in this entry, we're storing the POIDs of the ActorRole objects, not the ActorRole data. There is no data duplication in the cache. From the cached data shown in the diagram, we can easily rehydrate the entire object graph for the movie without the chance of any inconsistent results.
In addition to caching entities, NHibernate can also cache query results. In the cache, each query is associated with an array of POIDs for the entities of the query returns, similar to the way our movie actor collection is stored in the previous image. The entity data is stored in the entity cache. Again, this eliminates the chance of inconsistent results.
The third part of the cache stores a last-updated timestamp for each table. When data is first placed in the cache, the timestamp is set to a value in the future, ensuring the cache will never return uncommitted data from a pending transaction. Once the transaction is committed, the timestamp is set back to the present, allowing that data to be read from the cache.
There are some basic requirements when using the cache:
connection.provider
configuration property as in the Using dynamic connection strings recipe in Chapter 7.