本節內容數據庫
當許多人試圖同時修改數據庫中的數據時,必須實現一個控制系統,使一我的所作的修改不會對他人所作的修改產生負面影響。這稱爲併發控制。安全
簡單的理解就是2個或多個用者同時編輯相同的數據。這裏的用者多是:實際用戶、不一樣服務、不一樣的代碼段(使用多線程),及其在斷開式和鏈接式狀況下可能發生的狀況。多線程
併發控制理論根據創建併發控制的方法而分爲兩類:併發
一個鎖定系統,能夠阻止用戶以影響其餘用戶的方式修改數據。若是用戶執行的操做致使應用了某個鎖,只有這個鎖的全部者釋放該鎖,其餘用戶才能執行與該鎖衝突的操做。這種方法之因此稱爲悲觀併發控制,是由於它主要用於數據爭用激烈的環境中,以及發生併發衝突時用鎖保護數據的成本低於回滾事務的成本的環境中。app
簡單的理解一般經過「獨佔鎖」的方法。獲取鎖來阻塞對於別的進程正在使用的數據的訪問。換句話說,讀者和寫者之間是會互相阻塞的 ,這可能致使數據同步衝突。測試
在樂觀併發控制中,用戶讀取數據時不鎖定數據。當一個用戶更新數據時,系統將進行檢查,查看該用戶讀取數據後其餘用戶是否又更改了該數據。若是其餘用戶更新了數據,將產生一個錯誤。通常狀況下,收到錯誤信息的用戶將回滾事務並從新開始。這種方法之因此稱爲樂觀併發控制,是因爲它主要在如下環境中使用:數據爭用不大且偶爾回滾事務的成本低於讀取數據時鎖定數據的成本。優化
(以上摘自SQL Server2008 MSDN文檔)spa
NHibernate提供了一些方法來支持樂觀併發控制:在映射文件中定義了<version> 節點和<timestamp>節點。其中<version> 節點用於版本控制,代表表中包含附帶版本信息的數據。<timestamp>節點用於時間截跟蹤,代表表中包含時間戳數據。時間戳本質上是一種對樂觀鎖定不是特別安全的實現。可是一般而言,版本控制方式是首選的方法。固然,有時候應用程序可能在其餘方面使用時間戳。hibernate
下面用兩幅圖顯示這兩個節點映射屬性:線程
看看它們的意義:
下面用一個例子來實現樂觀併發控制,這裏使用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; } }
<?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>
具體參數:[Version] [int] NOT NULL 默認值爲1,固然了修改數據庫是最原始的方式了,若是你會使用SchemaExport,能夠直接利用持久化類和映射文件生成數據庫,之後在介紹如何使用這個。
在測試以前,咱們先看看數據庫中什麼數據,預知一下:
編寫併發更新測試代碼:
查詢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'
咱們再來編寫一個測試用於併發刪除。查詢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中的樂觀併發控制。