多用戶並發修改同一數據時,必須採用並發控制。NHibernate 原生支援樂觀並發,通過 <version> 或 <timestamp> 實現,版本號方式為首選。
| 類型 | 核心機制 | 適用場景 | 優缺點 |
|---|---|---|---|
| 悲觀並發 | 獨占鎖阻塞其他訪問 | 高衝突、鎖成本低於回滾 | 阻塞讀寫、易死鎖、擴展性差 |
| 樂觀並發 | 讀取不鎖、更新時檢查版本/時間戳 | 低衝突、回滾成本可接受 | 無阻塞、高併發、需處理衝突重試 |
NHibernate 採用樂觀並發,符合現代 Web 應用高併發特性。
<version name="Version" column="Version" type="integer" unsaved-value="0"/>
type:支援 Int64/Int32/Int16/Ticks/Timestamp/TimeSpanunsaved-value:新實例默認值(0),用於區分瞬時/持久狀態generated:never/always,是否由資料庫生成<timestamp name="LastModified" column="LastModified"/>
等價於 <version type="timestamp">,但版本號更安全、語義更清晰。
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; }
}
[Version] [int] NOT NULL DEFAULT 1,或使用 SchemaExport 自動生成。
[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); // 拋出 StaleObjectStateException
}
-- 1. 兩次查詢加載 Version=1
SELECT ... FROM Customer ... WHERE CustomerId=@p0;
-- 2. 第一筆更新成功(Version 1→2)
UPDATE Customer SET Version=2, Firstname='CnBlogs', Lastname='Lee'
WHERE CustomerId=1 AND Version=1;
-- 3. 第二筆更新失敗(WHERE Version=1 不匹配)
UPDATE Customer SET Version=2, Firstname='www.cnblogs.com', Lastname='Lee'
WHERE CustomerId=1 AND Version=1; -- 影響行數 0,拋出異常
核心機制:UPDATE 語句 WHERE 子句包含舊版本號,若已被修改則影響行數為 0,NHibernate 拋出 StaleObjectStateException。
[Test]
[ExpectedException(typeof(StaleObjectStateException))]
public void DeleteConcurrencyViolationCanotThrowException()
{
Customer c1 = _transaction.GetCustomerById(2);
Customer c2 = _transaction.GetCustomerById(2);
_transaction.DeleteCustomerTransaction(c1);
_transaction.DeleteCustomerTransaction(c2); // 同理失敗
}
DELETE FROM Customer WHERE CustomerId=2 AND Version=1; -- 第二次執行影響行數 0
版本號樂觀並發是 NHibernate 並發控制基石:
<version>,資料表加欄位StaleObjectStateException,業務層決定重試或提示下篇將探討事務管理與 Session 生命週期配合。