[轉]NHibernate之旅(7):初探NHibernate中的併發控制

本節內容數據庫

  • 什麼是併發控制?
    • 悲觀併發控制(Pessimistic Concurrency)
    • 樂觀併發控制(Optimistic Concurrency)
  • NHibernate支持樂觀併發控制
  • 實例分析
  • 結語

什麼是併發控制?

當許多人試圖同時修改數據庫中的數據時,必須實現一個控制系統,使一我的所作的修改不會對他人所作的修改產生負面影響。這稱爲併發控制。安全

簡單的理解就是2個或多個用者同時編輯相同的數據。這裏的用者多是:實際用戶、不一樣服務、不一樣的代碼段(使用多線程),及其在斷開式和鏈接式狀況下可能發生的狀況。多線程

併發控制理論根據創建併發控制的方法而分爲兩類:併發

悲觀併發控制(Pessimistic Concurrency)

一個鎖定系統,能夠阻止用戶以影響其餘用戶的方式修改數據。若是用戶執行的操做致使應用了某個鎖,只有這個鎖的全部者釋放該鎖,其餘用戶才能執行與該鎖衝突的操做。這種方法之因此稱爲悲觀併發控制,是由於它主要用於數據爭用激烈的環境中,以及發生併發衝突時用鎖保護數據的成本低於回滾事務的成本的環境中。app

簡單的理解一般經過「獨佔鎖」的方法。獲取鎖來阻塞對於別的進程正在使用的數據的訪問。換句話說,讀者和寫者之間是會互相阻塞的 ,這可能致使數據同步衝突。測試

樂觀併發控制(Optimistic Concurrency)

在樂觀併發控制中,用戶讀取數據時不鎖定數據。當一個用戶更新數據時,系統將進行檢查,查看該用戶讀取數據後其餘用戶是否又更改了該數據。若是其餘用戶更新了數據,將產生一個錯誤。通常狀況下,收到錯誤信息的用戶將回滾事務並從新開始。這種方法之因此稱爲樂觀併發控制,是因爲它主要在如下環境中使用:數據爭用不大且偶爾回滾事務的成本低於讀取數據時鎖定數據的成本。優化

(以上摘自SQL Server2008 MSDN文檔)spa

NHibernate支持樂觀併發控制

NHibernate提供了一些方法來支持樂觀併發控制:在映射文件中定義了<version> 節點和<timestamp>節點。其中<version> 節點用於版本控制,代表表中包含附帶版本信息的數據。<timestamp>節點用於時間截跟蹤,代表表中包含時間戳數據。時間戳本質上是一種對樂觀鎖定不是特別安全的實現。可是一般而言,版本控制方式是首選的方法。固然,有時候應用程序可能在其餘方面使用時間戳。hibernate

下面用兩幅圖顯示這兩個節點映射屬性:線程

節點屬性

看看它們的意義:

  • access(默認爲property):NHibernate用於訪問特性值的策略。
  • column(默認爲特性名):指定持有版本號的字段名或者持有時間戳的字段名。
  • generated:生成屬性,可選never和always兩個屬性。
  • name:持久化類的特性名或者指定類型爲.NET類型DateTime的特性名。
  • type(默認爲Int32):版本號的類型,可選類型爲Int6四、Int3二、Int1六、Ticks、Timestamp、TimeSpan。注意:<timestamp>和<version type="timestamp">是等價的。
  • unsaved-value(在版本控制中默認是「敏感」值,在時間截默認是null):表示某個實例剛剛被實例化(還沒有保存)時的版本特性值,依靠這個值就能夠把這種狀況和已經在先前的會話中保存或裝載的遊離實例區分開來。(undefined指明使用標識特性值進行判斷)

實例分析

下面用一個例子來實現樂觀併發控制,這裏使用Version版本控制。

1.修改持久化Customer類:添加Version屬性

public class Customer
{
    public virtual int CustomerId { get; set; }
    //版本控制
    public virtual int Version { get; set; }
    public virtual string Firstname { get; set; }
    public virtual string Lastname { get; set; }
}

2.修改映射文件:添加Version映射節點

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
   assembly="DomainModel" namespace="DomainModel">
  <class name ="DomainModel.Entities.Customer,DomainModel" table="Customer">
    <id name="CustomerId" column="CustomerId" type="Int32" unsaved-value="0">
      <generator class ="native"></generator>
    </id>
    <version name="Version" column="Version" type="integer" unsaved-value="0"/>
    <property name="Firstname" column ="Firstname" type="string" length="50" not-null="false"/>
    <property name ="Lastname" column="Lastname" type="string" length="50" not-null="false"/>
  </class>
</hibernate-mapping>

3.修改數據庫,添加Version字段

具體參數:[Version] [int] NOT NULL 默認值爲1,固然了修改數據庫是最原始的方式了,若是你會使用SchemaExport,能夠直接利用持久化類和映射文件生成數據庫,之後在介紹如何使用這個。

4.併發更新測試

在測試以前,咱們先看看數據庫中什麼數據,預知一下:

原始數據

編寫併發更新測試代碼:

查詢2次CustomerId爲1的客戶,這裏就是上面的第一條數據,第一個修改成"CnBlogs",第二個修改成"www.cnblogs.com",二者同時更新提交。你想一想發生什麼狀況?

[Test]
public void UpdateConcurrencyViolationCanotThrowException()
{
    Customer c1 = _transaction.GetCustomerById(1);
    Customer c2 = _transaction.GetCustomerById(1);
    c1.Name.Firstname = "CnBlogs";
    c2.Name.Firstname = "www.cnblogs.com";

    _transaction.UpdateCustomerTransaction(c1);
    _transaction.UpdateCustomerTransaction(c2);
}

讓咱們去看看數據庫吧,一目瞭然:

併發更新以後數據

咱們發現CustomerId爲1的客戶更新了FirstName數據,而且Version更新爲2。你知道什麼原理了嗎?看看這步NHibernate生成的SQL語句(個人可能比你的不同):先查詢數據庫,在直接更新數據,看看NHibernate多麼實在,明顯作了一些優化工做。

SELECT customer0_.CustomerId as CustomerId3_0_, 
customer0_.Version as Version3_0_, 
customer0_.Firstname as Firstname3_0_,
customer0_.Lastname as Lastname3_0_,
customer0_1_.OrderDiscountRate as OrderDis2_4_0_, 
customer0_1_.CustomerSince as Customer3_4_0_, 
case 
    when customer0_1_.CustomerId is not null then 1 
    when customer0_.CustomerId is not null then 0 
end 
as clazz_0_ FROM Customer 
customer0_ left outer join PreferredCustomer customer0_1_
on customer0_.CustomerId=customer0_1_.CustomerId 
WHERE customer0_.CustomerId=@p0; @p0 = '1'

UPDATE Customer SET Version = @p0, Firstname = @p1, Lastname = @p2 
WHERE CustomerId = @p3 AND Version = @p4;
@p0 = '2', @p1 = 'www.cnblogs.com', @p2 = 'Lee', @p3 = '1', @p4 = '1'

5.併發刪除測試

咱們再來編寫一個測試用於併發刪除。查詢2次CustomerId爲2的客戶,這裏就是上面的第二條數據,二者同時刪除數據。你想一想發生什麼狀況?

[Test]
[ExpectedException(typeof(NHibernate.StaleObjectStateException))]
public void DeleteConcurrencyViolationCanotThrowException()
{
    Customer c1 = _transaction.GetCustomerById(2);
    Customer c2 = _transaction.GetCustomerById(2);

    _transaction.DeleteCustomerTransaction(c1);
    _transaction.DeleteCustomerTransaction(c2);
}

同理,看看數據庫裏的數據,第二條數據不見了。

併發刪除以後數據

其生成SQL的查詢語句同上面同樣,只是一條刪除語句:

DELETE FROM Customer WHERE CustomerId = @p0 AND Version = @p1; @p0 = '2', @p1 = '1'

好了,這裏經過兩個簡單的實例說明了在NHibernate中對併發控制的支持。相信有了必定的瞭解,你們也能夠編寫一些有趣的測試來試試NHibernate中的樂觀併發控制。

相關文章
相關標籤/搜索