Sofire Suite 是一套個人從 2009 年 08 月開始着手研發的套件。歷經幾年的不斷優化改進,從最初的 V 套件到 Sofire2011 到目前的 Sofire.v1.5 概念版,Sofire 已經經歷了許多項目的考驗,並且出色的完成它的使命。現在,我將這套組件再次重構,嘗試讓它成為任意平台、框架、套件的的底層首選。秉着開源精神,希望這套組件在博友的討論中不斷成長、成熟。
本文主要介紹是概念版的—— SOFIRE XML SQL,我們稱之為 X-SQL。
目錄
前兩個禮拜,某個同事在公司內部,舉辦了一場小型培訓會。他給大家紹 JAVA 的一個開源套件——MyBatis(百度百科) 。
關於 Mybatis 含有一個非常重要的功能,就是 XML 化 T-SQL 語句(也成為動態 T-SQL)。它徹底的將 T-SQL 語句從代碼層上剝離,最大程度的動態化,也提高了產品的可維護性。
或許,這只是 Mybatis 的功能之一,我並沒有對它的其他功能進行深入研究。不過我卻對它的設計理念充滿興趣,培訓會結束后,我便搜尋有基於 .NET 版的 Mybatis。
Mybatis.NET,總體來說還算不錯,但它有一些與 JAVA 版的 Mybatis 不同,比如在條件判斷上他並不是采用 "test" 屬性進行驗證,而是通過條件 XML 元素(如 isNotNull XML 元素之類)進行判定。
<insert id="InsertAccountDynamic" parameterClass="Account"> INSERT INTO Accounts (Account_ID, Account_FirstName, Account_LastName, Account_Email) VALUES(#Id#, #FirstName#, #LastName# <dynamic prepend=","> <isNotNull prepend="," property="EmailAddress"> #EmailAddress# </isNotNull> <isNull prepend="," property="EmailAddress"> null </isNull> </dynamic> ) </insert>
<update id="updateStudent_if_set" parameterType="liming.student.manager.data.model.StudentEntity"> UPDATE STUDENT_TBL <set> <if test="studentName != null and studentName != '' "> STUDENT_TBL.STUDENT_NAME = #{studentName}, </if> </set> WHERE STUDENT_TBL.STUDENT_ID = #{studentId}; </update>
從上面的 XML 可以看出,Mybatis.NET 的 XML 表達式判定語法顯得十分臃腫,在多條件(如“userid=1 or userid=2”)無法友好的展示。所以……我的臭毛病又爆發了——“造輪子”。
好吧,我承認我的輪子已經造的過多了……
從整體功能方面,我盡可能的參考 Mybatis,但並不會失去 X-SQL 本身存在的特點(高效什么等,我就不扯蛋了):
- X-SQL 元素包含:xsql、expression(表達式)、if-else、switch-case-default、foreach、include(引用)、trim(修剪)、parameters(手動定義參數)
- 內置緩存機制(None、Names、Values、Forever)。
- 生成 Sofire.Data.ExecuteCommand 對象(不理解此對象的可以參考 此篇文章)。
- 支持參數綁定語法。拒絕拼接語法,參數化 T-SQL。當然,也支持非參數化參數綁定。這點與 Mybatis 完全不同。
- 支持 test 表達式以及自定義函數(解釋器是亮點):
0、關鍵字(所有關鍵字、參數、函數均匹配大小寫) and、or、not、true、false、null、if、E(檢查一下) 1、基本數據類型(Integer、String、DateTime、Float、Boolean) 2、支持 x:數字 s:字符串 d:日期 o:任意類型 數學函數 :Abs(x)、Acos(x)、Asin(x)、Atan(x)、Ceiling(x)、Cos(x)、Exp(x)、Floor(x) :IEEERemainder(x1,x2)、Log(x1,x2)、Log10(x)、Pow(x1,x2)、Round(x1,x2)、Sign(x) :Sin(x)、Sqrt(x)、Tan(x)、Truncate(x)、Max(x1,x2)、Min(x1,x2)、Floor(x) 字符串函數 :isEmpty(s)、len(s)、lower(s)、upper(s) :trim(s)、ltrim(s)、rtrim(s) :contains(s1,s2)、left(s1,s2)、right(s1,s2) :indexOf(s1,s2,[index],[count])、lastIndexOf(s1,s2,[index],[count]) :substr(s1,index,[length])、remove(s1,index,[length]) :replace(s1,s2,s3) :regexMatch(input,pattern,'[mir]') :regexSplit(input,pattern,'[mir]') :regexReplace(input,pattern,replacement,'[mir]') :concat(arg1,arg2,arg...,argN) 日期函數 :date([year],[month],[day],[hour],[minute],[second]) :year(d)、month(d)、day(d)、hour(d)、minute(d)、second(d)、millisecond(d) :dayOfWeek(d)、dateIn(d,beginDate,endDate)、dateAdd(d,x) 轉換函數 :toDate(s)、toInteger(s)、toFloat(s)、toString(o,[format])、toBoolean(s) 其他函數 :newGuid('N/D/B/P') :if(boolean,trueValue,falseValue) :in(o,arg1,arg2,arg...,argN) :isnull(o,defaultValue) 3、支持參數/函數的(預)定義,字符串拼接(+), 4、支持數字、浮點數、文本('string')、日期格式(#2012-01-01#) 5、支持以下運算符(一元、二元、三元): ! != % && & ( ) * + , - / : < << <= <> = == > >= >> ? ^ and not or | || ~
- 更多....
- document 元素下,表示一個 X-SQL 的生成函數。
- parameters 元素下,表示一個立即執行的 X-SQL 元素(這句話比較拗口,簡單的說,通過一個 SQL 生成遞增序列,添加到參數集合中)。
<?xml version="1.0" encoding="utf-8" ?> <document> <xsql name="selectAllStudent" cache="forever"> SELECT STUDENT_ID ,STUDENT_NAME ,STUDENT_SEX ,STUDENT_BIRTHDAY ,STUDENT_PHOTO FROM DEMO_STUDENT </xsql> </document>
- document 元素下,表示一個可調用的全局表達式(尚未實現,主要是存在性能問題,也可能是設計思路還未成熟)。
- parameters 元素下,表示一個立即解釋的表達式,並將解釋后的值添加到參數集合中。
<?xml version="1.0" encoding="utf-8" ?> <document> <xsql name="selectAllStudent" cache="forever"> SELECT STUDENT_ID ,STUDENT_NAME ,STUDENT_SEX ,STUDENT_BIRTHDAY ,STUDENT_PHOTO FROM DEMO_STUDENT </xsql> <xsql name="test_if"> <include path="selectAllStudent" /> <trim prefix="WHERE" trim="AND|OR"> <if test="studentID==1"> AND STUDENT_ID IN (1,2) </if> <else> AND STUDENT_ID=#{studentID} </else> <if test="studentName != null"> AND STUDENT_NAME=#{studentName} </if> </trim> </xsql> </document>
<?xml version="1.0" encoding="utf-8" ?> <document> <xsql name="selectAllStudent" cache="forever"> SELECT STUDENT_ID ,STUDENT_NAME ,STUDENT_SEX ,STUDENT_BIRTHDAY ,STUDENT_PHOTO FROM DEMO_STUDENT </xsql> <xsql name="test_switch" cache="values"> <include path="selectAllStudent" /> <switch test="studentName"> <case test="=='張三'"> STUDENT_ID=1 </case> <case test="=='李四'"> STUDENT_ID=2 </case> <case test="=='王五'"> STUDENT_ID=3 </case> <default> STUDENT_ID IS NOT NULL </default> </switch> </xsql> </document>
<?xml version="1.0" encoding="utf-8" ?> <document> <xsql name="selectAllStudent" cache="forever"> SELECT STUDENT_ID ,STUDENT_NAME ,STUDENT_SEX ,STUDENT_BIRTHDAY ,STUDENT_PHOTO FROM DEMO_STUDENT </xsql> <xsql name="test_foreach" cache="names"> SELECT * FROM STUDENTS <trim prefix="WHERE" trim="AND|OR"> <if test="studentSex!=null"> AND STUDENT_SEX=#{studentSex} </if> <if test="studentNames!=null"> AND STUDENTNAME IN( <foreach var="studentName" in="studentNames" trim=","> ,#{studentName} </foreach> ) </if> </trim> </xsql> </document>
<xsql name="insert_XML"> <parameters> <xsql name="student_id" cache="forever">select studentPKSequence.nextVal from dual</xsql> <expression name="student_id3">student_id+1</expression> </parameters> INSERT INTO STUDENTS(STUDENT_ID,STUDENT_NAME) VALUES(#{student_id,decimal},#{name,string}) </xsql>
我整理了 12 個實例。你可以通過以下 gif 小動畫來查看效果。

雖然我只進行了簡單的測試,但 X-SQL 的性能還是十分理想的。在 foreach(該元素最消耗性能) 2000 次測試下,采用緩存是 200~300 毫秒,非緩存效果下 500~600 毫秒(非最佳效果,采用 release,但是在VS 運行測試,而不是直接打開)。
在大多數情況下,我們建議采用 names(根據參數集合長度、參數名進行緩存)以及 forever(永久緩存)。而不建議使用 values(根據參數值緩存)。
其實不一定要采用緩存,通過上面的數據可以看出,緩存的后的性能只有一倍之差(緩存的性能損耗:Dict.TryGetValue,可見 X-SQL 的高性能),如果在非硬性要求下,除了 forever 緩存,其他緩存均不推薦。
壓力測試的必要性還是有的,能否經歷的起大項目的高頻率、多線程調用,我只能表示“我有信心”。
目前我將主要精力集中在:如何實現功能。對於性能等其他的要求,我只能延后。
關於源碼
我很猶豫要不要開放出去,其實從之前的文章大家可以看得出,我秉着開源原則,所以開源是必然,但文章發表后為什么不放上源碼,主要有兩個考慮:
- 你需要這份源碼嗎?
- 你會為這份源碼提出建議嗎?
如果有興趣的朋友很多的話,我會開放到 CodePlex。
最后,對博友 blue1000、 hpze2000 以及其他朋友說一聲抱歉,由於最近公司正在着手新產品的研發,我負責的文檔太多了,所以一直耽擱下來了。
但我很高興地告訴你們,關於 Sofire.DataComm.Remoting 以及 Sofire.DataComm.Net.Async 模塊已經完成,並且新增了許多功能!系列文章一定會寫出來的。
Sofire.DataComm下個版本計划(v2.0)
1、檢查高並發下是否存在死鎖(理論上暫時不存在),但在萬並發量出現死頓(懷疑是本地網絡問題?)。
2、增加 buffer 發送前和接收后的(加解密)和(解壓縮)。
************************* 2012-07-18 *************************
1、修復 ThreadPool.QueueUserWorkItem 在高頻發線程發生阻塞的問題。
2、增加屬性【ServiceVersion】,表示服務契約的版本信息,減少客戶端連接時返回契約數據。
3、增加屬性【SessionExpiration】,表示會話的最大超時時間。小於 0 則表示無超時,等於 0 表示不啟用。大於 0 則表示指定的超時秒數。默認為“20”秒。
4、優化內部代碼。
5、v1.5 版功能已完成。
我寫的很用心,如果您對這篇文章感興趣的話,請推薦……感謝!