NHibernate.3.0.Cookbook第一章第六节Handling versioning and concurrency的翻译
第一章第二节Mapping a class with XML
第一章第三节Creating class hierarchy mappings
第一章第四节Mapping a one-to-many relationship
第一章第五节Setting up a base entity class
Handling versioning and concurrency
版本控制和并发
在任何的多用户操作的系统中,为了处理并发更新以及版本问题,你必须在乐观并发控制和悲观并发控制中选择一种处理方式。在这一节中,我将给你展示怎样使用NHibernate去恰当地处理版本和乐观并发问题
准备工作
完成前面几节中的任务,包括Setting up a base entity class这节
如果去做
1.在Entity基类中,增加一个Version属性,代码如下所示:
{
public virtual TId Id { get; protected set; }
protected virtual int Version { get; set; }
public override bool Equals(object obj)
{
return Equals(obj as Entity<TId>);
}
2.在Product的映射文件中,增加version元素节点,name对应属性名称,代码如下所示:
<property name="Name" not-null="true" />
</natural-id>
<version name="Version" />
<property name="Description" />
<property name="UnitPrice" not-null="true" />
3. 在ActorRole映射文件中,也增加version元素节点
<generator class="guid.comb" />
</id>
<version name="Version" />
<property name="Actor" not-null="true" />
<property name="Role" not-null="true" />
分析原理
假如你有一个同时有两个用户操作数据库的应用程序,用户1和用户2同时获取相同的数据显示在他们的屏幕上,并且开始修改数据,用户1提交了他的修改后的数据至数据库中,一会儿,用户2也提交了他的修改后的数据。如果没有任何的并发访问检查,那么用户2的更新将会在不知不觉中覆盖了用户1的更新。这里有两个办法来防止这种情况的发生:乐观并发控制和悲观并发控制。
乐观并发控制的处理过程是这样的:当修改后的数据提交的时候,首先检查该数据是否已经被更新过。就上面的场景的来说,用户1和用户2开始修改他们的数据,用户1提交了他的修改后的数据,当用户2也提交了修改后的数据后,他的更新会是失败的,因为此时数据库中的该数据已经和用户2最初取得显示在屏幕上的数据已经不一致了。
下面的示例所示,我们有一个version字段来为一个实体跟踪这个更新的数据,Update如下所示:
SET Version = 2 /* @p0 */,
Name = 'Junk' /* @p1 */,
Description = 'Cool' /* @p2 */,
UnitPrice = 100 /* @p3 */
WHERE Id = '764de11e-1fd0-491e-8158-9db8015f9be5' /* @p4 */
AND Version = 1 /* @p5 */
NHibernate检查这个version的值是否和实体从数据库中加载数据时候的值是相同的,然后为该值加1,如果实体已经被更新过了,那么version字段的值不为1,并且该Update语句将不会更新任何行,NHibernate检测到该语句的更新结果返回的是0行记录就会抛出一个StaleStateException异常,意味着该实体的数据在内存中是过时的,不是最新的,已经和数据库失去了同步了。
补充知识
乐观并发控制之外的另一个选择就是使用悲观锁定。悲观锁定的过程是这样的:当一个用户获取数据的时候就给该数据加上一个独占锁,并且在他编辑该数据的时候也始终加上该独占锁,这样当用户1把数据显示在屏幕上进行编辑的时候,用户2就无法再获取同样的数据显示在屏幕上了。在上面的场景中,用户1取得数据,然后他拥有了对该数据的独占锁,用户2就无法再从数据库中读取该数据了。用户2的查询被阻塞,直至用户1释放了该独占锁或者查询超时。用户1在修改数据的时候不可避免地会有接听一个电话或者走开喝一杯咖啡的情况,这样的话将严重影响系统的并发访问。为了实现上述的悲观锁定,NHibernate需要你在应用程序事务中使用session.Lock。
乐观并发控制的其他方法
除了使用整形类型的version字段以外,NHibernate同样也允许我们使用基于DateTime类型的version字段。然而,Micorosoft SQL Server的datetime类型的精度为3毫秒,如果两个更新的提交非常的接近的话就会有问题了。最好是使用SQL Server 2008的 DateTime2日期类型,它拥有高达100纳秒的时间精度,或者也可以使用timestamp日期类型。
NHibernate也允许你使用传统的乐观并发控制的方式,可以通过设置optimistic-lock属性为dirty来实现,一个简单的示例看起来如下:
dynamic-update="true"
optimistic-lock="dirty">
在这种情况下,把Product对象的name从Stuff修改为Junk的SQL语句看起来如下所示:
SET Name = 'Junk' /* @p0 */
WHERE Id = '741bd189-78b5-400c-97bd-9db80159ef79' /* @p1 */
AND Name = 'Stuff' /* @p2 */
这就确保了用户读取数据后直到更新提交时Name的值没有被其他用户更新过(其他用户有可能已经更新过,如果已经更新过了,则上述SQL语句将不会更新任何行),然而,其他用户也可能已经更新了Product对象的其它字段的值了。
另外一个选择是设置optimistic-lock的值为all,这样设置以后,Product对象的自动产生的update语句看起来如下所示:
SET Name = 'Junk' /* @p0 */
WHERE Id = 'd3458d6e-fa28-4dcb-9130-9db8015cc5bb' /* @p1 */
AND Name = 'Stuff' /* @p2 */
AND Description = 'Cool' /* @p3 */
AND UnitPrice = 100 /* @p4 */
或许你已经猜到了,这种情况下,我们需要检验所有的属性的值,如果字段非常多的情况那是不太适合的。
当设置optimistic-lock的值为dirty时,dynamic-update的值必须设置为true。动态更新意味着update语句仅仅更新了变化了的属性值,而当dynamic-update设置为false时,update是在应用程序启动的时候就初始化了,它包含了实体的所有属性的值。