Enabling and Verifying NHibernate’s Second‑Level Cache: A Practical Guide
Learn how to turn on NHibernate’s second‑level cache, configure a provider, and verify hits in real code. Avoid common pitfalls and understand the limits of query and entity caching.
08 Jun 2026, 15:09 UTC

Why Enable the Second‑Level Cache?
The second‑level cache (2LC) is a process‑wide store that keeps entity instances, collection snapshots, and query results across Session boundaries. If the same data is read repeatedly in a stateless web application, the 2LC can cut database round‑trips by up to 70‑80 % in many scenarios. The cache is optional; you enable it only when you have read‑heavy workloads and can tolerate the consistency guarantees it offers.
Prerequisites and Provider Choice
To use the 2LC you need:
- A cache provider on the classpath – the most common choices are Ehcache, Infinispan, and Hazelcast. Each provider implements
org.hibernate.cache.spi.RegionFactoryand supplies its own configuration file. - Hibernate 5.2+ (or 6.x) because the API changed in 5.3. The configuration keys differ slightly between versions, but the concepts stay the same.
- A shared SessionFactory across your application. If each request builds its own factory, the 2LC will be ineffective because each factory has its own cache instances.
Configuration Example
Below is a minimal hibernate.cfg.xml that turns on the 2LC and configures Ehcache as the provider. Replace the placeholders with your actual values.
<hibernate-configuration version="5.4">
<session-factory>
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/mydb</property>
<property name="connection.username">user</property>
<property name="connection.password">pass</property>
<property name="cache.use_second_level_cache">true</property>
<property name="cache.use_query_cache">true</property>
<property name="cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<property name="cache.default_cache_concurrency_strategy">read-write</property>
<property name="net.sf.ehcache.configurationResourceName">/ehcache.xml</property>
</session-factory>
</hibernate-configuration>
Place ehcache.xml in your classpath root. A typical Ehcache file might look like:
<ehcache>
<cache name="com.example.domain.Product"
maxEntriesLocalHeap="1000"
timeToLiveSeconds="120"
eternal="false"
overflowToDisk="false"/>
</ehcache>
Entity and Collection Cache Strategies
Each mapped entity or collection must declare a cache strategy. This is the first place that can trip developers: if you forget the <cache> element, the entity will still be loaded from the database on every session, even though the 2LC is enabled.
<class name="com.example.domain.Product" table="PRODUCT">
<id name="id" column="ID" type="int">
<generator class="identity"/>
</id>
<property name="name" column="NAME"/>
<property name="price" column="PRICE"/>
<!-- Cache configuration -->
<cache usage="read-write"/><!-- or nonstrict-read-write, read-only, transactional -->
</class>
For collections:
<set name="orders" table="PRODUCT_ORDER" cascade="all" inverse="true" fetch="lazy">
<key column="PRODUCT_ID"/>
<many-to-many column="ORDER_ID" class="com.example.domain.Order"/>
<cache usage="nonstrict-read-write"/>
</set>
Query Cache Basics
Enabling cache.use_query_cache allows HQL and Criteria queries to store their result sets. To make a query cacheable:
var session = sessionFactory.OpenSession();
var list = session.CreateQuery("from Product p where p.price > :minPrice")
.SetParameter("minPrice", 100)
.SetCacheable(true) // Enable query cache for this statement
.List<Product>();
Every distinct query string becomes a cache region. The provider will use the region name derived from the query string. If you want finer control, you can add SetCacheRegion("productByPrice").
Verifying Cache Activity
Once you have the configuration in place, you need to confirm that hits are occurring. There are three practical ways:
- Hibernate Statistics
Make surevar stats = sessionFactory.Statistics; Console.WriteLine($"2LC hits: {stats.SecondLevelCacheHitCount}"); Console.WriteLine($"2LC misses: {stats.SecondLevelCacheMissCount}");sessionFactory.StatisticsEnabled = truein the config. - Provider Logs
If you enableorg.hibernate.cacheat DEBUG level, the log will print "Cache hit" or "Cache miss" messages for each region. - Manual Re‑query Test
Open two separate sessions. Load the same entity in the first session, close it, then load the same entity in the second session. The second load should hit the cache if the strategy isread-writeornonstrict-read-write.
Common Mistakes to Avoid
- Forgetting
<cache>in the mapping. Without it, the entity is never cached. - Using
read-onlyfor data that changes. This can lead to stale reads and no cache eviction. - Enabling
cache.use_query_cachebut not setting a cache region for the query. The default region may not be created, resulting in missed hits. - Not sharing the SessionFactory. Each factory has its own cache; a per‑request factory defeats the 2LC.
- Using a distributed cache provider without configuring proper replication or eviction, which can break coherence in clustered environments.
Limitations and Coherence Considerations
The 2LC is not a substitute for a full distributed cache. If multiple application nodes write to the same table, the cache can become stale unless the provider supports replication or you manually evict affected regions.
- Mutable Entities: With
read-writestrategy, updates go through a write‑through mechanism that locks the cache entry. In high‑concurrency scenarios,nonstrict-read-writemay be faster but can return stale data. - Query Cache Staleness: The query cache stores only the result set, not the entity state. If an underlying row changes, the cached result may still be returned unless you evict the region on update.
- Native SQL: The query cache does not automatically support native SQL unless you explicitly set
SetCacheable(true)and provide a region name.
Practical Checklist
- Enable
cache.use_second_level_cacheandcache.use_query_cacheinhibernate.cfg.xml. - Choose and configure a provider (Ehcache, Infinispan, etc.).
- Declare
<cache usage="..."/>on every entity/collection that should be cached. - Mark HQL/Criteria queries as cacheable and optionally set a region name.
- Verify statistics and provider logs after a few queries.
- Test cache coherence by updating an entity in one session and reloading it in another.
- Monitor for stale data in a production environment; adjust strategy or enable distributed replication if needed.
When followed carefully, NHibernate’s second‑level cache can dramatically improve read performance with minimal code changes. Keep an eye on statistics and logs to ensure the cache behaves as expected, and remember that the 2LC is most effective for read‑heavy, low‑write workloads.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.