三層架構已是非常經典的架構,稍微對開發有了解的人都知曉,為了解耦而把程式碼分在表示層(UI)、業務邏輯層(BLL)和資料連線層(DAL)。在我懵懂之期,把對資料庫連結建立、引數傳入和執行SQL這些操作都放到了DAL層,而把SQL編寫,引數繫結、還有結果獲取這些都塞在BLL層。這是不對的,在我後來接觸到真正的系統開發時,首次聽到SQLHelper這個概念才慢慢對DAL層有了真正的認識。本篇專門討論我見過的各種DAL層的底層,這個底層主要是封裝了每次都資料庫執行查詢(不一定是SELECT、還包括其他DDL甚至DML)所必要的資料庫連線處理,開閉連結,事務處理等操作。
SQLHelper
如果一個DAL的底層按最懶最粗略來說,可以從網上找一個SQLHelper,按照自己的習慣去修改用到專案中。SQLHelper是微軟一個開源專案PET Shop裡面實現了對SQL Server資料庫操作。實際上是對DbComand的ExecuteNonQuery、ExecuteReader和ExecuteScalar方法進行了封裝,免除了每次查詢都要開發人員去開啟、關閉資料庫連結,為DbCommand繫結DbParameter。
當然按我自己的偏好,原始的SQLHelper封裝的方法對我來說呼叫起來就相對繁瑣,於是我針對這類方法都過載了,免除了ConectionString,CommandType這些引數,同時也對執行SELECT查詢獲取一個DataTable這樣的操作多封裝了一類方法:ExecuteTable,這樣查詢起來就不用每次從DbDataReader中獲取資料了。
實習時遇到的第一個框架的DAL
這個DAL的底層我最開始看不明白,被借用到我的畢業設計裡面。到最近看程式碼的時候找回來看,最開始還是看不怎麼明白,後來突然闊然開朗。
有三個類DBUnitBase+DBUnit+DBControler,DBUnitBase是基類,DBUnit是繼承了DBUnitBase,DBControler是有一個欄位是DBUnit型別。他們的職責分別是
-
DBUnitBase:資料庫工具類的基類,包含了一個資料庫連線,封裝了對資料庫的所有操作,包含開啟/關閉資料庫、ExecuteNonQuery、ExecuteReader,此外包含了額外的執行SQL指令碼。
-
DBUnit:繼承了DBUnitBase,額外定義了根據給定的連結字串或者從配置檔案中的連結字串中建立一個資料庫連線。
-
DBContoler:負責對包含的資料庫連結進行操控,這個操控是在事務層面上的,因為每次查詢資料庫都是包含在一個事務中,具體的方法包括開啟事務BeginTransaction、提交事務CommitTransaction和回滾事務RollbackTransaction。
個人認為這幾個類是SQLHelper類的改裝+事務控制而已。DBUnitBase雖然是基類,但對於SQLHelper的方法全部都實現了,而且只針對一種資料庫,它的子類DBUnit只負責對DBUnit的構造和連結建立,兩者之間只是職責分離,單純是物件構造和功能使用這兩方面的分離,並沒有把資料庫的操作抽象出來,為各種資料庫的具體提供模板,這使得擴充套件性比較低,可能是不考慮使用其他資料庫的原因吧!DBContoler這個類給我感覺抽象性還不錯,因為我不用管它究竟是哪一種資料庫,它單純負責把資料庫的事務開啟、提交或回滾。
自己參與搭建的框架的DAL
這個是我工作兩年後首次參與搭建的框架,當時因為有個同事提了要相容Access資料庫這個需求,於是我就乾脆把MySQL、SQLite和Oracle都相容進去了,槽點多多,也是先看看裡面包括的類和他們的關係,這裡涉及的類比較多
這個底層的思路是這樣的:我把各種型別資料庫的查詢操作都抽象到一個介面中:IDBHelper,各種具體資料庫都對實現這個介面,從而對具體資料庫操作的進行實現,而每種資料庫的查詢引數型別都不一樣,於是我額外定義了一個公用的引數類,由DbParameterConventer轉換成各種資料庫的引數,具體的轉換操作由對應資料庫的ParameterConvert進行轉換。查詢時傳入的SQL語句會通過一個SQLCommandAdapter傳入,實際上他一個包含了四個字串型別屬性的類而已,每個欄位代表了一個資料庫型別,想要查詢資料庫時,傳入這次查詢中用到的各種資料庫對應的SQL,連同引數傳入到BaseDAL_Ex中,BaseDAL_Ex的會按照這次傳入的SQL通過DBHelperProvider按需選擇資料庫Helper和ParamterConvert。查詢時用到的連結字串均從配置檔案中獲取,這些連結資訊經過DBConnectConfigHandler讀取分析成ConnectInfo,儲存在ConnectInfoCollector中,在BaseDAL_Ex的各種方法都過載了一個需要提供連結名的版本,按照這個連結名去尋找相應的連結字串,否則就去使用預設的連結字串進行資料庫連線。
這個DAL底層的缺陷在於仍然缺乏物件導向程式設計的意識,雖然把公共部分抽象成介面或抽象類,但是在匹配呼叫的時候還是用了大量的If…Else去判斷,沒有藉助工廠模式去減縮,雖然在一定程度上我和另一位同事都認為不可缺少If..Else的判斷,但是這裡的If…Else中的程式碼量過大,有必要去減縮一下。
和其他公司交流時遇到的資料層
這個底層出自於某公司的大牛之手,這個DAL底層已經是一個精簡版的ORM,我看程式碼就只看了ADO.NET那一部分,ORM那部分還沒時間看,自己想把與以上幾個都是屬於純ADO.NET範疇內的先對比總結一下,弄清楚了再走下一步。
這裡主要是用了微軟的企業庫EntLib,在結合了幾個類:Dao,GenericDao,DaoFactory,DatabaseDao。最開始看這份原始碼的時候我頭腦有點繞暈,由於當時也不太瞭解EntLib,一直找不到DbConnection這個類所在地,下面就介紹一下各種類的結構。
-
Dao:資料持久層的入口,是一個抽象類,也使用了單例模式,把繼承它的一個具體類外放出來,呼叫Dao所定義的所有抽象方法,Dao中定義的抽象方法則包括ADO.NET部分使用的ExecuteNonQuery、ExecuteProcedure、QueryReader等,還有ORM的一些方法。這些方法供繼承它的具體類進行重寫。
-
GenericDao:繼承了Dao抽象類,重寫了Dao中所有定義的虛方法,也就是實現了資料庫查詢方法,這個查詢是廣義的查詢,但是這個實現只是一種外殼實現罷了,真正的查詢方法則又放到了抽象方法裡面留給子類去實現,這些外殼實現的查詢方法統一呼叫了方法去獲取連結,生成SQL語句,具體的執行操作就由虛方法去執行,執行查詢後按需要返回的型別返回int則是int,返回datatable則是datatable,返回datareader則返回datareader,因為這是一個泛型方法,各種查詢的差異它不需要去關心,這些細節都是由子類去實現的。
-
DatabaseDao:這個類繼承了GenericDao,就是實現了由GenericDao定義的虛方法,這些虛方法都是負責根據給定的DbCommand和SQL去執行查詢,應為執行的方式可能有差異,而這個DatabaseDao是針對了企業庫資料訪問框架去實現的,所以這裡實現的查詢方法實現形式都按照企業庫的方式去一一實現。
-
DaoFactory:這個類構造了EntLib企業庫的DataBase,也是作為了Dao的工廠去構造Dao。同時是DAL層中的快取器,其中的一個快取內容就是Dao。當抽象的Dao在外部被單例訪問呼叫資料庫查詢時,Dao就會呼叫這個DaoFactory去構造相應的Dao去執行這個查詢(在這個專案裡面暫時就只有使用了企業庫的DatabaseDao),查詢的方法一如既往地去處理SQL語句和引數,需要資料庫連線了,又通過DaoFactory獲取Dao(這裡其實也有可能包含了構造新物件的可能,但一般不會了,因為在第一次呼叫工廠時已經構造了)裡面的資料庫連線,最終實現資料查詢。
在這幾個類相互互動中感覺被弄得團團轉,因為單純Dao有三重繼承,而這三個類都與DaoFacotry互動,通常在呼叫的過程中又會涉及到Dao的呼叫,所以呼叫方霎時又成了被動方。不過還一個角度去看的話貌似會清晰一點,他們各自都履行了各自的職責,
-
Dao這個類角色最為複雜,因為它本身是基類,提供了外部訪問Dao的方法,在自身又要規定Dao需要做的事情,而且又要對資料庫進行儲存。專屬的事就是提供了外部訪問Dao的方法。
-
接著實現要Dao做得是由DenericDao進行初步實現,它只是抽取了各種操作中必須進行的操作統一去實現了,而各種操作中存在差異的,可能會出現不同實現形式的,就外放到它的子類去做了。
-
所以DatabaseDao是實現這種差異性資料庫查詢的一個子類,按現在來說暫時就單純這種實現,它這一套是使用了企業庫的。
-
在Dao呼叫方和Dao自身都不涉及到Dao的構造和資料庫的構造,因為這些都是由DataFactory去執行,這裡我認為是使用了控制反轉的思想。
在這個框架裡面的亮點是用了微軟的EntLib,這個免除了一大堆各種資料庫型別的DbHelper和DbParameter的相容轉換。但我強迫症地認為還是使用了If…Else的判斷,估計這個是沒法避免了,即便是用了工廠。對於這個框架可能我沒了解透,有內容理解錯了還是有可能的,這將在後續更改,閱讀這個框架的剩餘內容有新發現的也會在繼續寫寫博文。