摘要
在同一时刻数据访问量和更新次数比较大的系统中,产生了数据的并发访问问题。并发访问使得在这样的环境中,所有用户(程序、实际用户、进程、线程等)的操作不产生负面问题。
如果不使用并发,在两个用户同时写同一条数据的时候,最后结果是不确定的。不使用并发的时候两个客户同时删除同一条数据,将产生异常终止程序。
并发控制方式有两种:悲观并发控制和乐观并发控制,NHibernate使用乐观并发控制。这篇文章首先介绍这两种策略,然后详细介绍NHibernate的乐观并发控制。
1. 悲观并发控制和乐观并发控制
悲观并发控制
悲观并发控制是基于控制锁的一种并发控制,在对一条数据的所有用户操作之前都加一把并发锁。在一个用户要对一条数据进行修改之前给这条记录加一把排他锁,其他用户要修改此记录必须等待,只有在前一个用户更新完该记录之后,或者主动释放锁,这个锁才解开,其他用户才能够重新得到这把锁,更新这条记录。
因为读者和写者阻塞,写者和写者阻塞,所以他可能产生数据争用冲突,而且效率一般比较低。
悲观并发控制主要用于数据争用比较激烈的环境,而且锁的成本低于数据回滚的成本的情况下。
乐观并发控制
乐观并发控制是基于版本号或者时间戳的一种并发控制。对所有需要进行并发控制的数据上加一个版本号或者时间戳的字段或者属性,当产生争用冲突的时候,检验数据版本是否最新。如果不是最新,则回滚当前用户对数据的写操作。
乐观并发控制主要用于数据争用不那么激烈的环境,偶尔数据争用时回滚事务的成本低于锁的成本的情况下。
2. NHibernate的乐观并发控制
NHibernate默认使用乐观并发控制,默认给所有NHibernate实体类对象添加了一个“程序看不见”的Version属性,通过持久化对象的版本进行并发控制。
但是我们可以通过给实体关系映射文件中添加并发控制字段(version或timestamp)来显示地观察NHibernate是怎样进行乐观并发控制的。
以version为例:
<version name="属性名" column="列名" type=".Net数据类型" unsaved-value="对象临时态时主键值"/>
- name为必填属性
- colonm名称如果和属性名相同时可以省略
- type类型是.Net基本类型时可以省略
- type类型是.Net基本数据类型时,NHibernate能够自动推断主键属性默认值,因此可以省略unsaved-value
timestamp是可以理解为不够强的版本控制,以对象的最后更新的时间戳为当前最新版本。
程序演示
给Customer表添加字段Version,类型为int,不为空,默认值为1。
数据库数据的初始值
修改Customer.hbm.xml文件,添加version节点
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateDemoApp" namespace="NHibernateDemoApp"> <class name="Customer" table="Customer"> <id name="Id"> <generator class="native"/> </id> <version name="Version"/> <property name="FirstName" not-null="true"/> <property name="LastName" not-null ="true"/> <property name="AverageRating"/> <property name="Points"/> <property name="HasGoldStatus"/> <property name="MemberSince"/> <property name="CreditRating" type="CustomerCreditRating"/> <property name="Street"/> <property name="City"/> <property name="Province"/> <property name="Country"/> </class> </hibernate-mapping>
修改Customer类,添加Version属性
1 public class Customer 2 { 3 public virtual int Id { get; set; } 4 public virtual int Version { get; set; } 5 public virtual string FirstName { get; set; } 6 public virtual string LastName { get; set; } 7 public virtual double AverageRating { get; set; } 8 public virtual int Points { get; set; } 9 public virtual bool HasGoldStatus { get; set; } 10 public virtual DateTime MemberSince { get; set; } 11 public virtual CustomerCreditRating CreditRating { get; set; } 12 public virtual string Street { get; set; } 13 public virtual string City { get; set; } 14 public virtual string Province { get; set; } 15 public virtual string Country { get; set; } 16 }
1)修改操作的乐观并发控制
修改Main函数
1 static void Main(string[] args) 2 { 3 HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize(); 4 5 using (var session = SessionFactory.OpenSession()) 6 { 7 var customer1 = session.Get<Customer>(1); 8 var customer2 = session.Get<Customer>(1); 9 10 customer1.LastName = "Chen"; 11 customer2.LastName = "Liu"; 12 session.Update(customer1); 13 session.Flush(); 14 session.Update(customer2); 15 session.Flush(); 16 } 17 18 Console.WriteLine("Completed"); 19 Console.ReadLine(); 20 }
打开NHibernate Profile,清空Session,执行程序,得到结果。
看到虽然调用了两次Session.Update和Session.Flush,但是实际上只执行了一次Update语句。只有最后一次Update被执行,第一次Update操作被自动回滚了。生成的Update语句中的查询条件增加了Version=1这个条件。
执行后数据库记录:
2)删除操作的乐观并发控制
修改Main函数
1 static void Main(string[] args) 2 { 3 HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize(); 4 5 using (var session = SessionFactory.OpenSession()) 6 { 7 var customer1 = session.Get<Customer>(3); 8 var customer2 = session.Get<Customer>(3); 9 10 session.Delete(customer1); 11 session.Delete(customer2); 12 session.Flush(); 13 } 14 Console.WriteLine("Completed"); 15 Console.ReadLine(); 16 }
这里只用了一次Session.Flush,因为在第一次Flush后,持久化对象从持久化对象集合中删除了,此后调用Session.Delete方法会因为在持久化对象集合中找不到该对象而抛出异常。
清空NHibernate Profile的Session,执行程序,得到结果.
虽然调用了两次Session.Delete,但是只执行了一次Delete的SQL语句。
Id等于3的记录被删除了
作者:丹尼大叔
出处:http://www.cnblogs.com/uncle_danny
本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。