NHibernate read-write cache strategy isolation level verification
0 reputation · 20 Jun 2021, 08:04 UTC
0 reputation · 20 Jun 2021, 08:04 UTC
The read-write concurrency strategy for NHibernate's second-level cache expects the database transaction isolation level to be at least repeatable read. This ensures that soft locks used by the cache correctly prevent stale reads when concurrent transactions modify cached entities.
NHibernate provides the hibernate.connection.isolation property (accepting ADO.NET IsolationLevel integer values such as 4096 for RepeatableRead) but does not validate that the active database connection actually honors this setting. Some ADO.NET providers or connection pools ignore the property, and the default isolation level for SQL Server, PostgreSQL, and Oracle is read committed.
There is no built-in startup check or runtime assertion that confirms the effective isolation level matches the cache strategy's requirement. A mismatch can silently return stale entities from the cache after a concurrent commit, without throwing an exception or logging a warning.
What is the reliable way to confirm that the configured isolation level is actually enforced by the provider and connection pool at runtime? How can an application detect a mismatch between the declared hibernate.connection.isolation value and the database session's effective isolation level? Should a custom validator be added to the NHibernate integration test suite to fail fast when the isolation level is insufficient for the chosen cache strategy?
34775 reputation · 20 Jun 2021, 18:39 UTC
NHibernate will not tell you whether the isolation level you requested actually took effect, so you must ask the database. The reliable approach is to open a session, run a provider-specific SQL query against the live connection, and compare the reported isolation level with what your read-write cache strategy requires (at least Repeatable Read). Wrapping that check in a startup integration test that fails the build is the right way to fail fast.
hibernate.connection.isolation is a request passed to the ADO.NET provider when a transaction is started. It is not a guarantee:
Because a mismatch produces stale cache reads rather than exceptions, the failure mode is silent. That is exactly why verification must be explicit.
Run the check through the NHibernate session's connection so you measure the same session the cache will use:
using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
var level = session.CreateSQLQuery(
"SELECT CASE transaction_isolation_level " +
"WHEN 3 THEN 'RepeatableRead' WHEN 4 THEN 'Serializable' " +
"ELSE 'Insufficient' END " +
"FROM sys.dm_exec_sessions WHERE session_id = @@SPID")
.UniqueResult<string>();
tx.Rollback();
// assert level is RepeatableRead or Serializable
}Equivalents for other engines:
SHOW transaction_isolation; (note this reflects the default; check inside an explicit transaction for the active level).SELECT @@transaction_isolation;Important: run the query inside a transaction, because some providers only apply the requested level when a transaction begins. Querying an idle connection can report the server default and give you a false failure (or false pass).
Yes. A small integration test that opens a session through the real ISessionFactory (same configuration, same connection string, same pool as production), executes the provider query, and asserts the level is Repeatable Read or higher catches configuration drift, provider upgrades, and pool behavior changes. Run it in CI against the real database engine, not an in-memory substitute, since the whole point is verifying provider and server behavior.
nonstrict-read-write with Read Committed is acceptable for your consistency tolerance.TransactionScope) transactions, the effective level comes from the scope, not the NHibernate property — verify that path too.Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.