三層架構已是非常經典的架構,稍微對開發有了解的人都知曉,為了解耦而把代碼分在表示層(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的判斷,估計這個是沒法避免了,即便是用了工廠。對於這個框架可能我沒了解透,有內容理解錯了還是有可能的,這將在后續更改,閱讀這個框架的剩余內容有新發現的也會在繼續寫寫博文。