Can anyone explain optimistic locking in the context of NHibernate? (Using NHib 2.1.1.)
I’ve been running tests and my results are counter-intuitive. I have a versioned entity:
<class name="Test.Entity, Test" table="tblEntity" abstract="false" optimistic-lock="version">
<id name="Id" column="scheduleId" access="property" unsaved-value="0" type="Int64">
<generator class="hilo">
<param name="table">tblHiloUId</param>
<param name="column">nextHighValue</param>
<param name="max_lo">100</param>
</generator>
</id>
<version column="version" name="Version" type="Int32" unsaved-value="0" />
<property name="Prop1" column="prop1" update="false"
access="property" not-null="false" type="Boolean"
optimistic-lock="true" />
<property name="Prop2" column="isDefaultOverridable"
access="field" not-null="true" type="String"
optimistic-lock="true" />
</class>
And the following test:
[Test, ExpectedException(ExceptionType=typeof(StaleObjectStateException))]
public void SavingUpdatesOptimisticLockShouldThrow()
{
var cfg = new NHibernate.Cfg.Configuration();
cfg.AddAssembly("Test");
cfg.Configure();
var sessionFactory = cfg.BuildSessionFactory();
var sess1 = sessionFactory.OpenSession();
var sess2 = sessionFactory.OpenSession();
sess1.BeginTransaction();
sess2.BeginTransaction();
// NOTE: I get the same results if I load with Lock.None
// A record is loaded in the DB in test setup, assigned to m_Id
var a = sess1.Load<Entity>(m_Id);
var b = sess2.Load<Entity>(m_Id);
a.Prop2 = "New test value, session1”;
sess1.Save(a);
sess1.Transaction.Commit();
b = "Another, session2";
sess2.Save(b);
sess2.Transaction.Commit(); // Should throw?
}
After reading the docs, this is what I’d expect to see:
Both instances start with version=1. When I save and commit a, I see that its version number is incremented from 1 to 2, while b still has version=1 (as I’d expect). I’d expect that the call to sess2.Transaction.Commit() should throw, because NHibernate will determine that the record was updated since b was loaded, so optimistic concurrency issue. But it doesn’t – b commits fine, and overwrites changes saved when a was saved.
If I load explicitly selecting the lock I want, it does work as I’d expect and I get my exception.
This is surprising to me. Ayende noted in a concurrency blog post (http://ayende.com/Blog/archive/2009/04/15/nhibernate-mapping-concurrency.aspx) that using a version column should result in the generated UPDATE SQL statement to compare against the version number – and if the version doesn’t match the original version, we should get a StaleObjectException.
Can anyone clarify what’s going on here?
Thanks.
Remi.
Sorry, just noticed a typo in the code from the email. Corrected it and re-sent.
Remi.
b.Prop2 = "Another, session2";
sess2.Save(b);
sess2.Transaction.Commit(); // Should throw?
}
After reading the docs, this is what I’d expect to see:
Both instances start with version=1. When I save and commit a, I see that its version number is incremented from 1 to 2, while b still has version=1 (as I’d expect). I’d expect that the call to sess2.Transaction.Commit() should throw, because NHibernate will determine that the record was updated since b was loaded, so optimistic concurrency issue. But it doesn’t – b commits fine, and overwrites changes saved when a was saved.
If I load explicitly selecting the lock I want, it does work as I’d expect and I get my exception.
This is surprising to me. Ayende noted in a concurrency blog post (http://ayende.com/Blog/archive/2009/04/15/nhibernate-mapping-concurrency.aspx) that using a version column should result in the generated UPDATE SQL statement to compare against the version number – and if the version doesn’t match the original version, we should get a StaleObjectException.
Can anyone clarify what’s going on here?
Thanks.
Remi.
--
You
received this message because you are subscribed to the Google Groups
"nhusers" group.
To post to this group, send email to nhu...@googlegroups.com.
To unsubscribe from this group, send email to
nhusers+u...@googlegroups.com.
For more options, visit this group at
http://groups.google.com/group/nhusers?hl=.
Fabio,
There wouldn’t be two transactions on the same thread, but I’m working on a web app, so different worker threads would be running using different sessions. I did not think setting up a test that uses two different threads to be necessary to test the scenario – instead, I just used two different sessions, which is close enough to how it would work in production anyhow.
I don’t think this is an invalid question to be asking. The app’s on a web server, and concurrent requests are processed by different worker threads, aren’t they? Setting up a test that uses two distinct threads would be unnecessary and unreliable, since it would be difficult to ensure correct timing between the two threads for the behaviour I want to see. As a result, I believe the test I setup is actually quite appropriate for what I want to check.
No?
Remi.
Thanks Socratees.
It does work with the lock mode explicitly specified – I’ve tried that. In the NHib docs, they refer to this (loading with an explicit lock mode) as pessimistic locking (http://nhforge.org/doc/nh/en/index.html#transactions-optimistic, section 10.6). However, the behavior I’m seeing when specifying the lock mode looks like optimistic locking to me (as described in section 10.4). That is: grab the record, assume that you can edit it, and when it’s time to save, check the version – if it was updated by someone else, throw an exception due to conflict.
In my test below:
· If I load the instances specifying a lock mode myself (the NHib docs call “pessimistic locking”), it looks to me like it’s doing optimistic locking;
· If I don’t specify the lock mode when loading the isntances (which NHib calls “optimistic locking”), it looks to me like it isn’t doing anything at all.
I assume that I don’t have a proper understanding of locking in this context, and I was hoping that someone could clarify it for me.
Remi.
Clearly I don’t have a correct understanding of how it works then. Can I bother you to clarify where I’m wrong, so I can learn? I’m not looking for an answer to a specific problem, I want to understand.
Regards,