浅谈企业软件架构(5)
第五章 并發(fā)和事務
?
并發(fā)和事務是企業(yè)開發(fā)中常遇到的棘手問題,尤其對于新人來說有的時候他們是一個難以琢磨的名詞,但是企業(yè)開發(fā)中總會跟它們打交道,它們?nèi)缬半S形總會在某個時候成為開發(fā)者夢魔。本章我們通過一些簡單的例子來說明并發(fā)和事務的一些基本概念。
5.1 常見的并發(fā)情況
如果我們在多線程或多進程中操作同一數(shù)據(jù),就會遇到并發(fā)問題。企業(yè)開發(fā)中系統(tǒng)常常訪問的是存儲在數(shù)據(jù)庫中的業(yè)務數(shù)據(jù),我們最常見的例子就是兩個用戶在相隔很短的時間內(nèi)先后從數(shù)據(jù)庫中獲取了一份相同的某個業(yè)務單據(jù)的數(shù)據(jù)拷貝,用戶都完成各自的修改后分別向系統(tǒng)提交數(shù)據(jù),這樣一個簡單的場景就導致了更新數(shù)據(jù)的并發(fā)問題之一,更新丟失。
5.1.1. 更新丟失
在前面的例子基礎上來演示更新丟失的情況,在UnitTest項目中的CustomerBizTest.cs增加一個測試方法LostUpdateTest來測試更新丟失的并發(fā)情況,代碼如下:
?
Codepublic?void?LostUpdateTest()????????{
????????????CustomerBiz?customerBizA?=?new?CustomerBiz();?//新創(chuàng)建一個CustomerBiz?A對象
????????????Customer?customerA?=?customerBizA.Get(_id);
????????????//?新創(chuàng)建另一個CustomerBiz?B對象
????????????//?目前的CustomerDal實現(xiàn)會創(chuàng)建一個新的NHibernate會話,來模擬兩個用戶訪問同一個數(shù)據(jù)。
????????????CustomerBiz?customerBizB?=?new?CustomerBiz();
????????????Customer?customerB?=?customerBizB.Get(_id);
????????????//A用戶修改數(shù)據(jù),并提交數(shù)據(jù)
????????????customerA.Lastname??=?"吳";
????????????customerBizA.Edit(customerA);?//提交customer?A的修改
????????????//B用戶修改數(shù)據(jù),也隨后提交數(shù)據(jù)?
????????????customerB.Address?=?"China";
????????????customerBizB.Edit(customerB);?//提交customer?B的修改
????????????CustomerBiz?customerBizC?=?new?CustomerBiz();?//新創(chuàng)建一個CustomerBiz?C對象
????????????Customer?customerC?=?customerBizC.Get(_id);
????????????Console.WriteLine(customerC.Lastname);
????????????Console.WriteLine(customerC.Address);
????????????Assert.AreNotEqual(customerC.Lastname,?"吳");
????????????Assert.AreEqual(customerC.Address,?"China");
????????}
? 我們來看測試結(jié)果和測試的輸出:
?
測試結(jié)果和輸出都證明了我們的斷言,用戶A的修改customerA.Lastname?= "吳" 最后提交到數(shù)據(jù)庫時其更新丟失,修改值被后面用戶B的更新覆蓋掉了,用戶A的更新就永遠的丟失了。
?
5.1.2. 不一致的讀
不一致的讀的場景,企業(yè)開發(fā)中常見的多表記錄維護的業(yè)務數(shù)據(jù)(如:報賬單),報銷人員A在第一次錄入完報賬單后,子表總共有5條記錄,數(shù)據(jù)提交回系統(tǒng)。這時經(jīng)理B啟動了系統(tǒng)并讀到了這張報賬單準備進行審核,數(shù)據(jù)裝載到了經(jīng)理B的電腦終端上,隨后經(jīng)理B還沒來得及仔細看報賬單詳細內(nèi)容,就接到了一個電話,在電話里他跟對方聊了一會。報銷人員A保存完數(shù)據(jù)后,發(fā)現(xiàn)報賬單明細(子表)有一個金額錯誤于是他重新修改了這條記錄,把金額從1562.42元修改成15620.42元,隨后把修改提交回系統(tǒng)。經(jīng)理B接完電話仔細看了完報賬單沒什么問題審核了該單據(jù)。通常我們的單據(jù)審核的狀態(tài)會放在主表記錄里面,經(jīng)理B審核了一份單據(jù)金額相差1萬多元的報賬單!
不一致的讀導致了上面的這種局面,經(jīng)理B審核的單據(jù)與系統(tǒng)最后報銷人員A提交的單據(jù)存在不一致的數(shù)據(jù)。
5.1.4. 隔離數(shù)據(jù)操作
上述兩種情況都會導致業(yè)務數(shù)據(jù)正確性的失敗,從而導致系統(tǒng)錯誤行為。通過數(shù)據(jù)隔離可以避免上述兩種并發(fā)的基本情況,一個用戶讀取數(shù)據(jù)后,別的用戶不能在讀取數(shù)據(jù),或者只能只讀讀取數(shù)據(jù)。企業(yè)開發(fā)中常用單據(jù)狀態(tài)來對單據(jù)數(shù)據(jù)進行過濾,如:草稿狀態(tài),上面的例子里如果使用草稿狀態(tài),經(jīng)理B是不能查看到報銷人員A未正式提交的狀態(tài)的報銷單。
通過隔離數(shù)據(jù)操作來避免正確性失敗。但是只考慮數(shù)據(jù)的正確性是不夠的,隔離操作也會導致了一個僅僅瀏覽數(shù)據(jù)的用戶鎖住了數(shù)據(jù)導致真正要修改業(yè)務數(shù)據(jù)的用戶要等他瀏覽完數(shù)據(jù)后才能更改業(yè)務單據(jù)。企業(yè)開中我們也要考慮數(shù)據(jù)使用的靈活性,即多少個并發(fā)活動可以同時發(fā)生。
5.1.4. 樂觀并發(fā)控制和悲觀并發(fā)控制
樂觀并發(fā)控制在上述場景下,會給經(jīng)理B提示他提交的數(shù)據(jù)與系統(tǒng)當前的拷貝不一致,他需要重新加載數(shù)據(jù)來進行審核,避免造成系統(tǒng)正確性失敗的錯誤。樂觀并發(fā)控制是關于沖突檢測,并提示用戶接下來如何操作。悲觀并發(fā)控制如果使用在上述場景中就是報銷人員A在經(jīng)理B讀取報賬單后不能再修改他的報賬單據(jù)了。如果他確實需要修改單據(jù)必須等到審核完單據(jù)后,去找經(jīng)理B取消審核該單據(jù),然后再來修改自己的報賬單。悲觀并發(fā)控制可以看成隔離數(shù)據(jù)的操作來避免并發(fā)操作的產(chǎn)生。樂觀并發(fā)控制需要導致提交沖突的用戶放棄自己的數(shù)據(jù)修改,犧牲自己前面的工作。
???
5.2 事務
企業(yè)開發(fā)中處理并發(fā)最主要的工具就是事務,通常使用ACID來描述軟件事務。
原子性:在一個事務里,所有的操作都必須全部完成。要么全部成功,要么回滾所有操作。常見的例子就算是企業(yè)開發(fā)中的入庫單單據(jù),入庫單如果某物料A入庫數(shù)據(jù)量為100,當前物料A的庫存數(shù)據(jù)量為30,那么在入庫單提交回系統(tǒng)的同時,當前庫存物料A的紀錄也需要把庫存書更新為30+100=130,部分完成不是事務的概念。
一致性:事務開始和完成的過程中,系統(tǒng)的其它資源必須是一致的,沒有被改變的狀態(tài),也就是事務中不能有其他事務改變系統(tǒng)的資源狀態(tài)。
隔離性:事務成功完成后,其提交的數(shù)據(jù)才能被其他事物操作讀取本次事務結(jié)果。
持久性: 已提交的事務必須是永久保存的。
5.2.1. 系統(tǒng)事務
系統(tǒng)事務常說的就是由關系數(shù)據(jù)庫系統(tǒng)一組SQL命令的組合。如下:?
Code=?980;????????INSERT?INTO?Customer?(Firstname,?Lastname,?Gender,?Address,?Remark,?Active,?CustomerId)?
????????VALUES?('Howard',?'Wu',?'男',?'中國',?'',?0,?100);
????UPDATE?Customer?SET?Firstname?=?'Howard',?Lastname?=?'吳',?Gender?=?'男',?Address?=?'中國',?
????????Remark?=?'',?Active?=?0?WHERE?CustomerId?=?100;?
????DELETE?FROM?Customer?WHERE?CustomerId?=?'101';
END?TRY
BEGIN?CATCH
????SELECT?
????????ERROR_NUMBER()?AS?ErrorNumber
????????,ERROR_SEVERITY()?AS?ErrorSeverity
????????,ERROR_STATE()?AS?ErrorState
????????,ERROR_PROCEDURE()?AS?ErrorProcedure
????????,ERROR_LINE()?AS?ErrorLine
????????,ERROR_MESSAGE()?AS?ErrorMessage;
????IF?@@TRANCOUNT?>?0
????????ROLLBACK?TRANSACTION;
END?CATCH;
IF?@@TRANCOUNT?>?0
????COMMIT?TRANSACTION;
GO
? 在事務里任何一個命令如果執(zhí)行失敗了就必須回滾所有前面執(zhí)行的SQL命令,只有都執(zhí)行成功了才提交到數(shù)據(jù)庫,最終完成系統(tǒng)事務。
?
5.2.2. 業(yè)務事務
業(yè)務事務就是前面舉的入庫單的例子,我們須在系統(tǒng)事務執(zhí)行業(yè)務事務才能把業(yè)務事務變成我們業(yè)務系統(tǒng)中的事務處理,來實現(xiàn)客戶的業(yè)務事務?,F(xiàn)在我們回過頭來看我們的前面的例子,我們的Dal層都是針對一個Model來設計的,然后由Biz層來調(diào)用Dal層提交Model數(shù)據(jù)。我們把針對單個Model的系統(tǒng)事務實現(xiàn)在了Dal層??墒遣僮鞫鄠€Model對象的業(yè)務事務是在Biz層實現(xiàn)的,這樣就給我們Dal層設計出了一個難題,至少現(xiàn)在我們的Dal層是不能實現(xiàn)這樣的支持,增、改、刪除方法都默認啟動了事務,我們需要重構(gòu)我們得代碼來實現(xiàn)Biz層多Model提交的業(yè)務事務支持。
這樣的設計是基于業(yè)務事務是由業(yè)務層(Biz)來封裝的,也只有在Biz層最應該負責在哪兒啟動事務哪兒提交事務,以及什么情況下回滾事務。因為Nhibernate的系統(tǒng)事務是由它的Session來啟動的。我們需要增加一個專門管理事務的類封裝事務的啟動、提交和回滾。避免Biz層直接引用Nhibernate的Session來實現(xiàn)業(yè)務事務要求(數(shù)據(jù)訪問層的實現(xiàn)邏輯對于Biz層是不可見的)。?
?
5.2.3. 重構(gòu)Dal層代碼
我們增加一個類來專門管理NHibernate Session中的系統(tǒng)事務,同時在單Model提交的Dal中仍然有自己默認系統(tǒng)事務調(diào)用,這樣的寫法的方便性在于對于常見的單Model提交的系統(tǒng)事務由Dal層默認實現(xiàn),減少在Biz層的事務調(diào)用的繁瑣操作。
NHibSessionMgr.cs NHibernate事務管理類
?
Code?System;using?System.Collections.Generic;
using?System.Linq;
using?System.Text;
using?NHibernate;
namespace?Dal
{
????public?class?NHibSessionMgr
????{
????????#region?私有變量
????????private?static?NHibernate.ISessionFactory?_sessionFactory;
????????private?static?ISession?_session?;
????????#endregion
????????#region?構(gòu)造函數(shù)
????????private?NHibSessionMgr(){}
????????#endregion
????????private?static?ISessionFactory?GetSessionFactory()
????????{
????????????if?(_sessionFactory?==?null)
????????????{
????????????????NHibernate.Cfg.Configuration?cfg?=?new?NHibernate.Cfg.Configuration().AddAssembly("Model")
????????????????????.Configure();
????????????????_sessionFactory?=?cfg.BuildSessionFactory();
????????????}
????????????return?_sessionFactory;
????????}??
????????public?static?NHibernate.ISession?GetSession(bool?otherSession)
????????{
????????????if?(_sessionFactory?==?null)
????????????{
????????????????_sessionFactory?=?GetSessionFactory();
????????????}
????????????if?(otherSession)
????????????{
????????????????_session?=?_sessionFactory.OpenSession();
????????????}
????????????else
????????????{
????????????????if?(_session?==?null)
????????????????{
????????????????????_session?=?_sessionFactory.OpenSession();
????????????????}
????????????????else?if?(!_session.IsOpen)
????????????????{
????????????????????_session.Reconnect();
????????????????}
????????????}
????????????return?_session;
????????}
????????///?<summary>
????????///?獲取NHibernate?Session實例
?????????///?通過加載當前工程配置文件生成的SessionFactory,并創(chuàng)建Session
????????///?</summary>
????????///?<returns></returns>
????????public?static?NHibernate.ISession?GetSession()
????????{
????????????return?GetSession(false);
????????}??
????}
}
? 本類負責獲得NHibernate的會話對象,代碼中使用了單件模式,但是為了在某些場合下仍可創(chuàng)建新的會話,我們使用了帶參數(shù)的GetSession(bool otherSession)函數(shù)來實現(xiàn)返回新的話。?
NHibernateSession.cs代碼如下:?
Code?System;using?System.Collections.Generic;
using?System.Linq;
using?System.Text;
using?NHibernate;
namespace?Dal
{
????public?class?NHibernateSession
????{
????????#region?私有變量
????????private?bool?_otherSession?;
????????private?ITransaction?_trans?;
????????private??ISession?_session;?
????????#endregion
????????#region?會話對象
????????protected?ISession?Session
????????{
????????????get
????????????{
????????????????return?_session;
????????????}
????????}
????????#endregion
????????#region?構(gòu)造函數(shù)
????????public?NHibernateSession(bool?otherSession)
????????{
????????????_otherSession?=?otherSession;
????????????//獲得NHibernate會話對象
????????????_session?=?NHibSessionMgr.GetSession(otherSession);
????????}
????????public?NHibernateSession()
????????????:?this(false)
????????{
????????}
????????#endregion
?????public?void?CloseSession()
?????{
??????????_session.Close();
?????}
#region?事務處理(統(tǒng)一對事務進行管理)
????????///?<summary>
????????///?開始事務
????????///?</summary>
????????public?void?TransBegin()
????????{
????????????if?(_session.Transaction?!=?null?&&?_session.Transaction.IsActive)
????????????{
????????????????_trans?=?null;
????????????}
????????????else
????????????{
????????????????_trans?=?_session.BeginTransaction();
????????????}
????????}
????????///?<summary>
????????///?回滾事務
????????///?</summary>
????????public?void?TransRollBack()
????????{
????????????if?(_trans?!=?null)
????????????{
????????????????_trans.Rollback();
????????????????_trans?=?null;
????????????}
????????}
????????///?<summary>
????????///?提交事務
????????///?</summary>
????????public?void?TransCommit()
????????{
????????????if?(_trans?!=?null)
????????????{
????????????????_trans.Commit();
????????????????_trans?=?null;
????????????}
????????}
????????#endregion
????}
}
? CustomerDal.cs代碼如下:?
using?System.Collections.Generic;
using?System.Linq;
using?System.Text;
using?NHibernate;
using?NHibernate.Cfg;
using?NHibernate.Criterion;
using?Model;
namespace?Dal
{
????public?class?CustomerDal?:?NHibernateSession
????{
????????public?CustomerDal()
????????????:?base(false)?{?}????????
????????public?Customer?Get(Int32?customerId)
????????{
????????????Customer?customer?=?(Customer)Session.Get(typeof(Customer),?customerId);
????????????if?(customer?!=?null)
????????????{
????????????????return?customer;
????????????}
????????????else{?return?null;?}?
????????}
???????public?Boolean?Add(Customer?customer)
????????{
????????????TransBegin();
????????????try
????????????{
????????????????Session.Save(customer);
????????????????TransCommit();
????????????????return?true;
????????????}
????????????catch
????????????{
????????????????TransRollBack();
????????????????if?(Session.Contains(customer))
????????????????{
????????????????????Session.Evict(customer);
????????????????}
????????????????return?false;
????????????}
????????}
??????? public?Boolean?Edit(Customer?customer)
????????{
????????????TransBegin();
????????????try
????????????{
????????????????Session.SaveOrUpdate(customer);
????????????????TransCommit();
????????????????return?true;
????????????}
????????????catch
????????????{
????????????????TransRollBack();
????????????????if?(Session.Contains(customer))
????????????????{
????????????????????Session.Evict(customer);
????????????????}
????????????????return?false;
????????????}
????????}
????????public?Boolean?Delete(Customer?customer)
????????{
????????????TransBegin();
????????????try
????????????{
????????????????Session.Delete(customer);
????????????????TransCommit();
????????????????return?true;
????????????}
????????????catch
????????????{
????????????????TransRollBack();
????????????????if?(Session.Contains(customer))
????????????????{
????????????????????Session.Evict(customer);
????????????????}
????????????????return?false;
????????????}
????????}
????}
}
? 注意:上面重構(gòu)代碼的變化,事務調(diào)用我們直接調(diào)用了基類的統(tǒng)一封裝事務方法。Dal層的類需要從基類HibernateSession繼承而來,事務函數(shù)使用的是基類統(tǒng)一封裝的事務函數(shù),這是這次重構(gòu)中最關鍵的調(diào)整。這樣事務就不再直接使用Hibernate Session的事務。同時,為了能在單元測試中模擬不同會話的需要,我們的NHibernateSession類是可以通過構(gòu)造函數(shù)的otherSession參數(shù)來確定是否使用另個會話來進行測試,這個對于我們進行并發(fā)沖突測試很重要。
?
5.2.4. 重構(gòu)Biz層代碼
CustomerBiz.cs 只調(diào)整構(gòu)造函數(shù),目的也是確保可以打開另一會話來進行我們需要的單元測試或業(yè)務邏輯。????
Codepublic?CustomerBiz(?Boolean?otherSession?)????????{
????????????_customerDal?=?new?CustomerDal(otherSession);
????????}
????????public?CustomerBiz()
????????????:this(false)
????????{
????????????
????????}
?
5.2.5. 重構(gòu)更新丟失單元測試代碼?
Codepublic?void?LostUpdateTest()????????{
????????????CustomerBiz?customerBizA?=?new?CustomerBiz(true);?//新創(chuàng)建一個CustomerBiz?A對象
????????????Customer?customerA?=?customerBizA.Get(_id);
?????????//?新創(chuàng)建另一個CustomerBiz?B對象
?????????//?目前的CustomerDal實現(xiàn)會創(chuàng)建一個新的NHibernate會話,來模擬兩個用戶訪問同一個數(shù)據(jù)。
????????????CustomerBiz?customerBizB?=?new?CustomerBiz(true);
????????????Customer?customerB?=?customerBizB.Get(_id);
????????????//A用戶修改數(shù)據(jù),并提交數(shù)據(jù)
????????????customerA.Lastname??=?"吳";
????????????customerBizA.Edit(customerA);?//提交customer?A的修改
????????????//B用戶修改數(shù)據(jù),也隨后提交數(shù)據(jù)?
????????????customerB.Address?=?"China";
????????????customerBizB.Edit(customerB);?//提交customer?B的修改
????????????CustomerBiz?customerBizC?=?new?CustomerBiz(true);?//新創(chuàng)建一個CustomerBiz?C對象
????????????Customer?customerC?=?customerBizC.Get(_id);
????????????Console.WriteLine(customerC.Lastname);
????????????Console.WriteLine(customerC.Address);
????????????Assert.AreNotEqual(customerC.Lastname,?"吳");
????????????Assert.AreEqual(customerC.Address,?"China");
????????}
?
? 運行單元測試通過說明我們的重構(gòu)符合了預期的要求。
?
5.2.6. 實現(xiàn)業(yè)務事務
目前為止我們還沒有實現(xiàn)本章說的業(yè)務事務,也就是在一次業(yè)務事務中涉及到對多個Model實例的操作,最后需要確保業(yè)務事務被系統(tǒng)事務正確的提交到中,避免出現(xiàn)數(shù)據(jù)丟失或者完整性缺失等情形。業(yè)務事務都是在Biz層產(chǎn)生的,我們通過重構(gòu)Biz層代碼來實現(xiàn)多Model操作的系統(tǒng)事務調(diào)用。
我們增加一個BaseBiz基類來實現(xiàn)統(tǒng)一的事務調(diào)用。BaseBiz.cs代碼如下:
?
Code?System;using?System.Collections.Generic;
using?System.Linq;
using?System.Text;
using?Dal;
namespace?Biz
{
????public?class?BaseBiz
????{
????????private?NHibernateSession?_session?=?null;
????????private?bool?_otherSession;
????????#region?構(gòu)造函數(shù)
????????///?<summary>
????????///?構(gòu)造方法
????????///?</summary>????????
????????public?BaseBiz(bool?otherSession)
????????{
????????????_otherSession?=?otherSession;
????????????_session?=?new?NHibernateSession(otherSession);
????????}
????????public?BaseBiz()
????????????:?this(false)?{?}
????????#endregion
????????#region?事務處理(統(tǒng)一對事務進行管理)
????????///?<summary>
????????///?開始事務
????????///?</summary>
????????public?void?TransBegin()
????????{
????????????_session.TransBegin();
????????}
????????///?<summary>
????????///?回滾事務
????????///?</summary>
????????public?void?TransRollBack()
????????{
????????????_session.TransRollBack();
????????}
????????///?<summary>
????????///?提交事務
????????///?</summary>
????????public?void?TransCommit()
????????{
????????????_session.TransCommit();
????????}
????????#endregion
????}
}
?
現(xiàn)在我們假設有一個業(yè)務需要批量添加的用戶必須在一個事務里完成,也就是說我們必須保證批量添加的用戶數(shù)據(jù)要么都提交到系統(tǒng)中,要么全部回滾數(shù)據(jù),不允許部分數(shù)據(jù)提交的情形出現(xiàn)。
?
單元測試代碼如下:
?
Codepublic?void?AddCustomersTest()????????{
????????????Customer?customerA?=?new?Customer();
????????????customerA.CustomerId?=?101;
????????????customerA.Firstname?=?"Howard?A";
????????????customerA.Lastname?=?"Wu";
????????????customerA.Gender?=?"男";
????????????customerA.Address?=?"中國";
????????????Customer?customerB?=?new?Customer();
????????????customerB.CustomerId?=?102;
????????????customerB.Firstname?=?"Howard?B";
????????????customerB.Lastname?=?"Wu";
????????????customerB.Gender?=?"男";
????????????customerB.Address?=?"中國";
????????????IList<Customer>?list?=?new?List<Customer>();
????????????list.Add(customerA);
????????????list.Add(customerB);
????????????CustomerBiz?customerBizA?=?new?CustomerBiz(true);
????????????customerBizA.Add(list);
????????????list.Remove(customerA);
????????????list.Remove(customerB);
????????????//提交后,驗證數(shù)據(jù)是否提交成功
????????????CustomerBiz?customerBizB?=?new?CustomerBiz(true);
????????????Customer?customerA1?=?customerBizB.Get(101);
????????????Assert.AreEqual(customerA1.Firstname,?customerA.Firstname);
????????????Customer?customerB1?=?customerBizB.Get(102);
????????????Assert.AreEqual(customerB1.Firstname,?customerB.Firstname);
????????????//刪除本次成功提交的測試數(shù)據(jù)
????????????customerBizA.Delete(customerA);
????????????customerBizA.Delete(customerB);
????????????Customer?customerC?=?new?Customer();
????????????customerC.CustomerId?=?103;
????????????customerC.Firstname?=?"Howard?C";
????????????customerC.Lastname?=?"Wu";
????????????customerC.Gender?=?"男";
????????????customerC.Address?=?"中國";
????????????Customer?customerD?=?new?Customer();
????????????customerD.CustomerId?=?104;
????????????customerD.Firstname?=?"Howard?D";
????????????customerD.Lastname?=?"Wu";
????????????customerD.Gender?=?"男abc";??//屬性值超長,導致提交失敗,驗證數(shù)據(jù)是否全部回滾。
????????????customerD.Address?=?"中國";
????????????list.Add(customerC);
????????????list.Add(customerD);
????????????customerBizA.Add(list);
????????????//提交后,驗證數(shù)據(jù)是否全部回滾
????????????CustomerBiz?customerBizC?=?new?CustomerBiz(true);
????????????Customer?customerC1?=?customerBizC.Get(103);
????????????Assert.IsNull(customerC1);
????????????Customer?customerD1?=?customerBizC.Get(104);
????????????Assert.IsNull(customerD1);
????????}
?
CustomerBiz代碼重構(gòu)和增加批量添加函數(shù)如下:
?
Code?System;using?System.Collections.Generic;
using?System.Linq;
using?System.Text;
using?Model;
using?Dal;
namespace?Biz
{
????public?class?CustomerBiz?:?BaseBiz
????{
????????private?CustomerDal?_customerDal?;
????????public?CustomerBiz(?Boolean?otherSession?)
????????????:?base(otherSession)
????????{
????????????_customerDal?=?new?CustomerDal();
????????}
????????public?CustomerBiz()
????????????:?this(false)
????????{
????????}
????????public?Boolean?Add(Customer?customer)
????????{
????????????return?_customerDal.Add(customer);
????????}
????????public?Customer?Get(Int32?customerId)
????????{
????????????return?_customerDal.Get(customerId);????????????
????????}???????
????????public?Boolean?Edit(Customer?customer)
????????{
????????????return?_customerDal.Edit(customer);
????????}
????????public?Boolean?Delete(Customer?customer)
????????{
????????????return?_customerDal.Delete(customer);
????????}
????????public?void?Active(Customer?customer)
????????{
????????????customer.Active?=?1;
????????????_customerDal.Edit(customer);
????????}
????????public?bool?Add(IList<Customer>?customers)
????????{
????????????TransBegin();??//開始業(yè)務事務
????????????try
????????????{
????????????????foreach?(Customer?c?in?customers)
????????????????{
????????????????????_customerDal.Add(c);
????????????????}
????????????????TransCommit();?//提交業(yè)務事務
????????????????return?true;
????????????}
????????????catch
????????????{
????????????????TransRollBack();???//回滾業(yè)務事務????
????????????????_customerDal.CloseSession();?//提交失敗后關閉當前會話
????????????????return?false;
????????????}????
????????}
????}
}
?
運行單元測試通過,注意看單元測試代碼邏輯,我們假定了兩種情況一種是正常提交到系統(tǒng)后,我們使用新的會話來獲取數(shù)據(jù)驗證是否與新增的數(shù)據(jù)是否一致,還有另一段測試代碼我們設計了一種提交錯誤的場景,來檢驗提交的數(shù)據(jù)是否被全部回滾了,不存在部分提交的情況。我們的重構(gòu)實現(xiàn)了我們預期的功能。
?
5.3 結(jié)語
本章我們簡要的描述了軟件開發(fā)中并發(fā)和事務,并發(fā)會產(chǎn)生“更新丟失”和“不一致的讀”問題,這兩種情況都會導致數(shù)據(jù)正確性的失敗,系統(tǒng)出現(xiàn)錯誤的業(yè)務邏輯操作行為。如果沒有兩個人同時操作系統(tǒng)中相同的數(shù)據(jù),就不會有并發(fā)問題。通過隔離數(shù)據(jù)操作可以解決并發(fā)帶來的正確性問題,但是卻缺少了并發(fā)帶來的靈活性。樂觀并發(fā)和悲觀并發(fā)是兩種處理并發(fā)的機制,兩種機制各有優(yōu)缺點。樂觀鎖策略可以看成是關于沖突的檢測,悲觀所則是關于沖突的避免。他們倆選擇使用是看場景來的,如果沖突的頻率和嚴重性很小,或者客戶可以接受沖突導致的數(shù)據(jù)更新丟失,就采用樂觀鎖策略,它可以帶來很好的并發(fā)性。如果并發(fā)沖突導致的結(jié)果對于用戶來說是不可接受的,就只能使用悲觀鎖策略。
事務是企業(yè)開發(fā)中處理并發(fā)最主要的工具,事務經(jīng)常使用ACID來描述。系統(tǒng)事務主要是指由關系數(shù)據(jù)庫或事務系統(tǒng)所支持的事務,如:一組sql命令組合。系統(tǒng)事務對于業(yè)務系統(tǒng)用戶來說是沒有意義的,只有通過系統(tǒng)事務實現(xiàn)的業(yè)務事務對于用戶來說才有價值。如我們例子里的批量添加用戶是一個業(yè)務事務,它需要在一個系統(tǒng)事務中實現(xiàn)。
下一章我們將繼續(xù)描述幾個復雜的業(yè)務事務是如何通過系統(tǒng)事務來實現(xiàn)的。加深我們對業(yè)務事務與系統(tǒng)事務關系的理解。
轉(zhuǎn)載于:https://www.cnblogs.com/aaa6818162/archive/2009/07/31/1535808.html
總結(jié)
以上是生活随笔為你收集整理的浅谈企业软件架构(5)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hashtable:仅有两列的表
- 下一篇: cacls 使用方法