距離上篇《Step by Step-構建本身的ORM系列-開篇》的時間間隔的過久了,很對不住你們啊,主要是由於有幾個系列必須提早先寫完,才能繼續這個系列,固然我也在html
寫這幾個系列的過程當中,對ORM這個系列中的原來的實現的想法有了新的認識和改進,固然這些都不是說是很先進的思想或者認識,也多是你們見過的思路吧,但願後面我能在面試
寫設計模式系列的過程當中,穿插講解ORM系列,固然個人這個構建的系列,也只能說是很簡易的,本身平時開發個小應用工具或者什麼的,可能用他,由於是本身開發的嘛,畢竟sql
使用起來仍是比較順手的!符合本身的操做習慣嘛。數據庫
固然我寫這個系列的過程當中,也會有本身認識偏激的地方,或者思路不正確的地方,還請大夥多多指出和批評。我也是在我目前的項目中學習到了不少的寶貴的經驗,其實設計模式
咱們應該能看到ORM給咱們提供的方便和不便之處,咱們取其精華,剔除糟粕,不過這真的很難。我其實對一些流行的ORM的底層實現,研究的很少也不深,像Nhibernate,我緩存
只是瞭解Hibernate,是當時從JAVA中瞭解過來的,不深刻,Castle框架卻是用過一段時間,EntityFreamWork,我也沒有用過,只是象徵性的下載最新版本,體驗了下AOP的服務器
方式,我感受其實有不少的時候,咱們使用AOP的方式,可以改進咱們程序的靈活性。這塊可能還須要大牛們多多指點。架構
我理想的ORM是實現持久化的透明,這個怎麼理解呢?就是說我在程序的開發中,我不想在業務代碼中書寫相應的持久化操做,也不關心業務層中的去如何調用你的併發
ORM,去完成CRUD的操做。我只關心個人業務邏輯,這個有點像DDD(領域驅動開發)裏面的領域層了,只關心領域內部的業務邏輯,而不關心其餘的東西,這樣方便咱們快速框架
的抓住關注的東西,而儘可能讓與領域無關的東西不要影響業務領域邏輯的實現。
本篇主要開始講述《Step by Step-構建本身的ORM系列-數據訪問層》關於數據訪問層的部分,其實前面也對這塊的內容有了必定的介紹了,其實本篇就是教你如何完成
ORM中的數據訪問層的操做,這裏是提供統一的數據訪問方法的實現。固然這裏的操做仍是主要集中在數據庫的操做,包括如何根據實體對象返回實體的列表,包括生成SQL語
句的幾類實現,還包括一些實現後續的ORM的配置管理的維護,這裏就是提供可視化的XML文件的配置,這個具體怎麼來作呢?由於咱們平時針對ORM的使用,都是直接修改
XML文件,咱們能夠提供一個可視化的界面,讓那個用戶配置這些相應的設置。經過這些配置,咱們能夠實現數據庫的平滑的遷移,緩存應用的配置,包括其餘的一些相關設置信
息。整體來講這些操做均可以依託於,咱們這裏的數據訪問層來完成。
咱們來看看ORM中數據訪問層的重要做用和地位吧:
上圖咱們知道,咱們全部的相關功能的基礎,都是基於數據訪問層來作的,因此咱們處理好這個層的相關邏輯
後,後續的問題就會比較容易開展。下面咱們就會針對這些疑問開始
一個個的解決咱們的數據訪問層應該提供的相關功能!大致的功能應該有以下功能:
一、持久化的操做方法CUD。能夠擴展提供建立表+其餘的修改表等相關的自動腳本工具。提供持久化透明的方式。
二、提供緩存服務,將對象的相應映射信息緩存起來,這樣後續執行生成語句等操做,效率上會是很大的提高。
三、咱們在處理對象對於Update語句,應該能處理好只能更變化的信息,若是沒有發生變化,那麼咱們是否是不用執行更新操做了呢?減小數據庫的操做次數。
四、提供基礎的查詢方法,之後全部的基於ORM上的查詢基於這個查詢進行擴展。提供持久化透明的查詢方式。
五、併發和事務的控制。咱們這裏可能提供一個內部的版本號的方式來作,一旦修改過這個對象或者發生改變,任什麼時候候的操做,咱們都是針對這個版本號的記錄來作的,版本號
經過內部提供的方法來進行。
一、開篇。
二、摘要。
三、本文大綱。
四、ORM之數據訪問層分析。
五、ORM相關代碼實現。
六、本章總結。
七、系列進度。
八、下篇預告。
咱們先來針對上面的幾個問題,咱們給出實現思路,來分析下給出的思路的可行性和如何實現的解析。具體的代碼下節給出核心實現方案。
4.一、提供通用的持久化的操做
這個具體的解析在上篇中已經給出了相應的思路了,咱們經過在底層提供相應的方法來作。通常來講,對應數據庫的四種操做,咱們在數據訪問層,也提供了相應的語句的
自動構造的過程,具體的構造,咱們前面給出的實現方案是經過特性來實現,特性中定義具體的數據庫字段,類型,長度等一些列的參數。我這裏就不復述了,我這裏分析下咱們
這樣實現的好處。咱們知道繼承的方式是挺好的,我爲何這麼說,經過提供一個基類,基類中定義通用的CUD的操做方法,這樣只要是繼承這個類的子類,都會有CUD的操做
方法了,可是咱們爲了提供持久化透明的方案,那麼無疑,對於持久化的操做,咱們就不但願由業務邏輯層中的業務邏輯對象來完成,那麼如何來作呢?咱們經過數據訪問層,提
供統一的操做方法,讓服務層來完成業務對象的持久化操做。這樣就能實現,持久化透明的方案。
因此咱們能夠這樣來作,在數據訪問層中,咱們提供一個接口,接口中定義持久化操做的幾類方案,經過不一樣的實現配置,咱們能夠在XML配置文件中進行指定,咱們採用
重量級的ORM仍是輕量級的ORM,這樣咱們也理想的實現了低耦合的特性。
同時,對於不一樣的文件的操做,咱們能夠支持多文件類型的寫入,固然對於不一樣的數據庫存儲,若是咱們利用關係型數據庫咱們須要ORM,對於對象數據庫的操做,或者
XML文件的操做,咱們這時候的ORM就變了。
4.二、提供緩存服務
咱們知道,咱們第一篇中主要是經過自定義特性+反射的形式來處理:咱們並無提供完整的特性操做,其實還有不少的狀況,好比說,咱們還能夠提供對視圖的映射,提
供一個視圖的特性等。還有其餘的特性有不少,後續會給出完整的代碼結構,咱們知道自定義特性+反射,若是每次在將對象映射成數據庫表的時候,那麼效率上是多麼的低下
啊,那麼這個時候,咱們能夠考慮使用緩存的方式,將數據庫表中的數據庫列與對象中的屬性列進行映射,咱們能夠把這些對應關係放在緩存中,那麼若是咱們在後續的處理中遇
到與數據庫相關操做,須要進行對象映射的時候,咱們都先會去緩存中查找有沒有指定鍵值的映射數據庫表列存在,存在取出生成SQL語句,不然經過反射取出對應的數據庫表
列,放在緩存中。下面給出示意圖。將這個過程進行描述:
固然我這裏給出的確定是反向的根據對象生成操做數據的SQL語句的方式。這裏沒有考慮,當數
據庫表發生變化的時候,我應該自動同步緩存中的映射集合信息,固然咱們能夠經過必定的策略,來實現這樣的雙向同步問題。例如以下的方式可能就是可行的方案。
們經過同步組件,在每次進行數據操做以前,咱們能夠應用更好的策略,好比記錄或者遍歷文件的修改狀態,對比文件的最後修改日期,是否是發生修改,或者當某個文件發生修
改以後,咱們記錄在某個配置文件中,這樣咱們能夠提升同步的效率,由於經過這樣的方式,咱們不須要每次檢查對象是否是發生變化了,這樣咱們若是發現對象沒有發生變化,
那麼咱們就不要讓同步組件去檢測對象是否發生變化,這樣就能提升效率,同時支持當映射對象發生變化的時候,咱們不用修改咱們的關係數據庫。你們都知道,面向對象設計建
模與關係數據庫的最大難題就是雙方的變化的同步性的方案是很難定的,我這裏只是給出一個簡單的思路和方式,可能還有更好的方案,也請你們多多告訴我。
4.三、Update語句的操做
我不知道,大家在面試的時候,若是你常常開發底層的面向對象的ORM的時候,應該會遇到這樣的問題,咱們在查詢一個映射對象的時候,咱們可能只須要取出這個對象的
部分列,而不是所有的數據列,這個時候,咱們如何指定呢?就是填充對象的時候,咱們只須要填充指定的列?或者咱們須要在在保存編輯的時候,咱們不更新未發生變化的數據
庫,其實主要是要求,咱們在生成SQL語句的時候,咱們但願咱們的更新語句中不要出現,沒有發生變化的數據列的設置,這個如何作到呢?我想咱們能夠經過以下的2種方式來
作。
一、經過對象的序列化,來複制一個對象,而且系統中緩存這個對象,在編輯以前,緩存,等到提交後,釋放這個對象。這個由系統默認的提供方法。前提是標記對象是編
輯狀態,這樣在修改對象以前進行復制,否則若是修改完了,再複製就沒有什麼意義了
二、經過字典來保存更新數值的數據列,經過數據字典來存放。咱們在字典中存放映射的數據列,將發生變化的數據列和數據列的值進行標記發生改變,咱們在生成更新語
句的時候。直接遍歷這個集合,將列狀態發生改變的列生成相應的操做語句便可。這些都是可行的方式,咱們在前面的架構設計中也提到過的。都是給過思路的,這裏我也很少復
述了。
三、…。可能還有其餘的更好的方式,還請你們多提出好的思路,我備註在這個位置!
4.四、提供基礎的查詢方法。
這裏說的提供基礎的查詢方法,指的是基於數據庫操做之上,咱們提供幾個經常使用的查詢方法,包括自動生成版本號的方法等,咱們的版本號能夠經過日期+流水號的形式來
生成,或者是其餘的狀況。GUID也是可行的辦法。不過維護起來可能不是很方便。因此底層提供相應的操做方法更容易來作。
咱們這裏考慮提供以下的基礎查詢方法,複雜的查詢方法,咱們能夠提供一個入口來作。例如咱們提供以下幾類方法:
一、底層生成版本號的方法,自增ID流水號,根據不一樣的生成規則自定義設置ID生成規則,來組織生成ID的通用方法。
二、提供實體與數據庫行集之間的轉換,咱們須要將數據庫記錄轉換爲實體集合。經過查詢方法返回對象集合。這裏提供返回指定主鍵的對象集合。
三、返回一個數據表或者視圖中的全部記錄。
四、返回傳入分頁個數,和分頁排序字段,分頁條件的分頁集合。
五、返回指定列的查詢方法。(這裏沒有想到好的辦法,怎麼樣的形式比較靈活可以動態的指定返回的列,好比說1列,10列,5列等),但願你們提出好的意見和建議!
六、提供統一的入口,編寫SQL語句傳入到數據訪問層中進行查詢和檢索,根據指定的返回類型來返回泛型對象。
4.五、併發和事務控制
我想一個系統中必須考慮的就是事務處理了,咱們進行批量操做的時候,若是數據不一樣步,那就太痛苦了,也是不能使用的系統的,咱們但願咱們的ORM可以自動的集成
事務和併發,固然這裏說的併發是當用戶數上升到必定量的時候,就會產生這樣的問題,理論上來講只要有2個以上的用戶,就必須考慮併發操做!併發咱們有幾個控制的思路,
整體來講應該說說咱們前面的設計的內部的一個自動生成的版本號,是最好的選擇。具體怎麼個意思呢?咱們來解釋下:
對於併發控制,咱們知道,併發控制的問題:寫丟失,讀出來的數據是髒數據,無疑就是這麼2個比較常見的問題,那麼咱們如何來對寫丟失進行限制呢?目前通用的方案
都是經過樂觀鎖來處理,二我的能夠同時對某個信息進行讀取,可是隻能有一我的在進行編輯,可是最後修改的內容會把前面修改的信息覆蓋掉,這是樂觀鎖的處理方式。
悲觀鎖,則是隻要有人在修改,那麼可能你不能進行修改,也不能讀取,這種方式,固然能夠保證信息的修改的同步性和一致性,可是用戶的易用性和友好性方面不夠人性
化,相比來講,有人修改,就不能被其餘人修改,可是能夠讀取的方式體驗方面要差一些,不過各有使用的場景,通常來講,悲觀鎖是樂觀鎖的一個補充。
咱們這裏既不是樂觀鎖,也不是悲觀鎖的形式,經過版原本對某個記錄的版本進行記錄,一旦發生改變,那麼記錄的版本就要發生變化,咱們這裏對這個行集的版本的更新
能夠經過ORM提供的版本的生成規則來生成一個版本號,或者是經過觸發器來實現,固然性能也是咱們須要考慮的部分。
對於事務,我想通常的不是分佈式操做的應用,咱們經過數據庫提供的自己的事務服務來完成,基本上就能夠知足平常的需求,也沒有什麼特別難的地方,我想這裏我也就
不詳細的說了,咱們來簡單的說下,分佈式事務的一致性,對於這種分佈式的事務操做,咱們能夠採用離線併發模式來處理。這個怎麼理解呢?就是經過工做單元來實現。咱們把
每個操做看着一個工做單元,若是咱們在執行某個事務操做的過程當中,若是返回是0或者是其餘的不是咱們指望的結果時,咱們不會進行任何的提交操做,若是所有執行經過,
咱們循環全部的工做單元進行提交,不然咱們回滾全部的系統事務。咱們把這樣的分佈式事務,看做一個業務事務,由一些列的工做單元組成,這些工做單元看做是系統事務。
1,Create語句的實現:
private Dictionary<string, Column> _autoIncrementColumns = new Dictionary<string, Column>();
private Dictionary<string, Column> _updateColumns = new Dictionary<string, Column>();public string TableName
{
get
{
return string.Empty;
}
}public Dictionary<string, Column> UpdateColumns
{
get
{
return this._updateColumns;
}
set
{
this._updateColumns = value;
}
}public Dictionary<string, Column> AutoIncrementColumns
{
get
{
return this._autoIncrementColumns;
}
set
{
this._autoIncrementColumns = value;
}
}public virtual IDbCommand GetDbCommand()
{
// 若是column的值沒有被更新過,則返回null
if (this.UpdateColumns.Count == 0)
{
return null;
}ArrayList fieldList = new ArrayList();
ArrayList valueList = new ArrayList();
SqlCommand cmd = new SqlCommand();foreach (Column column in this.UpdateColumns.Values)
{
fieldList.Add("[" + column.Key + "]");
valueList.Add("@" + column.Value);
}string fieldString = string.Join(" , ", (string[])fieldList.ToArray(typeof(string)));
string valueString = string.Join(" , ", (string[])valueList.ToArray(typeof(string)));
string cmdText = string.Format("INSERT INTO [{0}]({1}) VALUES({2})",
this.TableName,
fieldString,
valueString);string sqlGetIndentityID = null;
if (this.AutoIncrementColumns.Count == 1)
{
sqlGetIndentityID = string.Format("SELECT [{0}] = SCOPE_IDENTITY()");
}if (sqlGetIndentityID != null)
{
cmdText = cmdText + " ; " + sqlGetIndentityID;
}cmd.CommandText = cmdText;
return cmd;
}
}
下面給出Update語句,是從上面的更新集合中編輯,將列的狀態發生改變的列添加生成到語句中-示例代碼以下:
public virtual IDbCommand GetDbCommand()
{
// 若是column的值沒有被更新過,則返回null
if (this.UpdateColumns.Count == 0)
{
return null;
}ArrayList fieldList = new ArrayList();
ArrayList valueList = new ArrayList();
SqlCommand cmd = new SqlCommand();string updateSQL=string.Empty;
foreach (Column column in this.UpdateColumns.Values)
{
if (column.State)
updateSQL += "[" + column.Key + "]=" + "@" + column.Value;
}string cmdText= string.Format("UPDATE {0} SET {1}={2}", updateSQL);
cmd.CommandText = cmdText;
return cmd;
}至於刪除的代碼比較簡單,我這裏就不給出刪除的代碼了,整體來講形式是相同的。
我有2篇關於緩存的介紹,緩存中最難搞的問題就是緩存的過時的問題,對應反射的性能問題也是存在過時的問題,好比說咱們的數據庫表發生變化,或者對象中的屬性發
生變化後,那麼咱們的緩存中的內容也須要進行更新,否則咱們生成的數據庫操做語句將會不正確。咱們這裏的策略就是將映射出來的對象,放在服務器中的緩存中,固然對於
B/S和C/S系統中可能採起的緩存方式和策略仍是有區別的。B/S咱們的緩存能夠採用緩存到服務器中,或者是經過緩存服務器來完成,通常是經過Remoting來將服務器與緩存
服務器完成通訊。咱們看看簡單的示例代碼吧:
public class Cache
{
private static System.Web.Caching.Cache cache = HttpRuntime.Cache;//這裏是默認取當前應用程序的服務緩存。public static object Get(string key)
{
return cache[key];
}public static bool Remove(string key)
{
return !(null == cache.Remove(key));
}public static void Set(string key, object value)
{
cache.Insert(key, value, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(3));
}
}
上面給出的緩存類的示例代碼,具體的操做,使用反射後,將反射後對象元數據信息緩存起來,經過對象名來緩存:
具體代碼以下:
PropertyInfo[] property = null;
if (Cache.Get("") != null)
{
property = (PropertyInfo[])Cache.Get("");
}
else
{
Type t = Type.GetType("");property = t.GetProperties();
}經過上面的幾行簡單的代碼就能表達出咱們上面講述的思路,具體如何過時,這個上面也給出了一些思路,可能大夥有更好的思路,我這裏就不班門弄斧了。
5.3,提供基礎的查詢服務
我想大夥對於查詢語句的操做,應該說是司空見慣了吧,咱們如何能更好的完成統一的查詢服務多是咱們關心的問題,我這裏不會給出多數據庫的實現,可是能夠給大夥
一個思路,咱們這裏定義返回的查詢命令的時候,若是說支持多數據的話,能夠定義一個統一的接口,不一樣的數據庫提供不一樣的實現接口,而後根據統一的ORM配置來調用不一樣的
組件來生成SQL語句,完成調用操做。
相關的查詢服務代碼以下:
/// <summary>
/// 系統自動生成的版本號
/// </summary>
/// <returns></returns>
public string GetVersion()
{
return DateTime.Now.Year.ToString() + DateTime.Now.Month.ToString() + DateTime.Now.Day.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString();
}public int GetMax<T>()
{
//根據T的類型獲取T的最大列,完成查詢操做。
string sqlText = " Select MAX(ISNULL(列名,0))+1 FROM TableName";return 0;
}public List<T> GetAll<T>()
{
//根據T的類型獲取T的最大列,完成查詢操做。
string sqlText = " Select * FROM TableName";return new List<T>();
}public List<T> GetList<T>(string condition,int pagesize,string orderField)
{
//根據T的類型獲取T的最大列,完成查詢操做。
string sqlText = " Select * FROM TableName where " + condition + " order by " + orderField;return new List<T>();
}
上面給出的不是所有的代碼,部分代碼仍是你們本身去完成吧,我這裏想的是,一些客戶比較複雜的自定義代碼經過一個接口傳入的形式,來完成基礎查詢服務的調用。
咱們這裏給出通用的接口定義:
CommandType CommandType
{
get;
set;
}string whereCondition
{
get;
set;
}string orderCondition
{
get;
set;
}string TableName
{
get;
set;
}Column[] ColumnList
{
get;
set;
}string SQL
{
get;
set;
}
給出默認幾類示例的實現:
public class BaseSQL : ISelect
{
public System.Data.CommandType CommandType
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}public string whereCondition
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}public string orderCondition
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}public string TableName
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}public Column[] ColumnList
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}public string SQL
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}具體調用的代碼以下:
public class SpecialSQL : BaseSQL
{
public void Test()
{
this.TableName = "";
this.SQL = " SELECT * FROM TEST ";
this.whereCondition = " ID=4 ";
this.orderCondition = " ORDER BY ID DESC ";
}
}固然這裏的繼承的方式不是很推薦,能夠採用抽象工廠的模式,來建立這個查詢對象,而後咱們在調用這個查詢對象的地方,咱們能夠自定義這個SQL查詢對象,後臺的
ORM自動解析,完成自定義SQL語句的統一查詢服務入口。
固然若是您有更好的方案,能夠提出來,很是感謝!
本文主要是講述ORM中的數據訪問層,我這裏因爲一些特殊的緣由,代碼給出的不是特別的詳細,一方面是因爲以前的那部分的代碼丟了,如今一時難以還原,因此形成有
些代碼給出的不是特別完整的狀況,請你們見諒,文章中的有些部分的內容,我在實現的過程當中也是遇到了很多的問題,我如今的具體問題列出來,也請你們幫我解決一下,個人
疑問,我目前在架構設計的過程當中遇到以下的問題:
一、我在實現服務層持久化透明服務的時候,我也想把查詢服務透明,意思就是業務對象與服務層只經過DTO來完成,業務對象的全部數據都經過服務層的訪問來完成。
二、若是如今有比較複雜的業務邏輯的操做語句的時候,個人這個SQL語句放在數據訪問層好呢?仍是放在哪裏?應該具體的職責劃分要明確。
三、我只是但願業務邏輯層處理業務數據,具體的業務數據怎麼來的,我想讓業務邏輯只關心DTO。
四、對於這樣的服務層提供的統一查詢方式的話,我在表現層調用的時候,如何傳參,可以很好的組織參數的傳遞和調用,傳統的方式再也不目前的考慮當中。
感謝大夥可以給出一些意見和建議!相信這個ORM系列能愈來愈好!