SOFIRE v1.5 概念版—— X-SQL


Sofire Suite 是一套個人從 2009 年 08 月開始着手研發的套件。歷經幾年的不斷優化改進,從最初的 V 套件到 Sofire2011 到目前的 Sofire.v1.5 概念版,Sofire 已經經歷了許多項目的考驗,並且出色的完成它的使命。現在,我將這套組件再次重構,嘗試讓它成為任意平台、框架、套件的的底層首選。秉着開源精神,希望這套組件在博友的討論中不斷成長、成熟。

本文主要介紹是概念版的—— SOFIRE XML SQL,我們稱之為 X-SQL。

目錄

什么是 X-SQL?

前兩個禮拜,某個同事在公司內部,舉辦了一場小型培訓會。他給大家紹 JAVA 的一個開源套件——MyBatis(百度百科) 。

關於 Mybatis 含有一個非常重要的功能,就是 XML 化 T-SQL 語句(也成為動態 T-SQL)。它徹底的將 T-SQL 語句從代碼層上剝離,最大程度的動態化,也提高了產品的可維護性。

或許,這只是 Mybatis 的功能之一,我並沒有對它的其他功能進行深入研究。不過我卻對它的設計理念充滿興趣,培訓會結束后,我便搜尋有基於 .NET 版的 Mybatis。

Mybatis.NET,總體來說還算不錯,但它有一些與 JAVA 版的 Mybatis 不同,比如在條件判斷上他並不是采用 "test" 屬性進行驗證,而是通過條件 XML 元素(如 isNotNull XML 元素之類)進行判定。

Mybatis for .NET 示例
    <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>
                    
Mybatis for JAVA 示例
    <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”)無法友好的展示。所以……我的臭毛病又爆發了——“造輪子”。

好吧,我承認我的輪子已經造的過多了……

X-SQL 支持什么功能?

從整體功能方面,我盡可能的參考 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	 	|	 	||	 	~	
    		
    
  • 更多....
X-SQL 元素
本節針對 X-SQL 的各類元素,進行簡單的講解(注意,元素區分大小寫,懶判斷模式)。
document 元素
document 元素是一個 .xsql 文件的根級樹節點。目前該元素沒有任何的屬性,因為目前我還沒有想好什么全局配置。
xsql 元素
xsql 元素,是 document 元素下的的一個重要元素,表示一個動態的 T-SQL 生成節點。xsql 可以放在兩個地方,表示不同的含義。
  1. document 元素下,表示一個 X-SQL 的生成函數。
  2. 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>
                    
expression 元素
expression 元素是一個特殊的元素,它表示一段表達式的解釋結果(結果可以是任意值,但對於 test 屬性,應該是 false)。同樣,expression 也可以放在兩個不同的地方,代表不同的含義。
  1. document 元素下,表示一個可調用的全局表達式(尚未實現,主要是存在性能問題,也可能是設計思路還未成熟)。
  2. parameters 元素下,表示一個立即解釋的表達式,並將解釋后的值添加到參數集合中。
if-else 元素
if 元素是一個判定元素,當表達式結果為 true 時,則編譯 if 元素下的 X-SQL 內容,否則編譯 else 元素下的內容(如果有)。
<?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>
                
switch-case-default 元素
同 if-else 元素一樣,switch 解釋與 case 之間的 test 結果(switch 也擁有 test 屬性,但不一定要填寫),根據判定條件編譯 case 元素下的內容,否則編譯 default 元素的內容。
<?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>
                    
foreach 元素
foreach 元素是一個非常強大的元素。它解釋一個集合類型(array、list等皆可)的參數,並賦予兩個“動態綁定參數”:index 和 var。根據 in 參數(集合)進行循環取出結果。注意,in 參數必須存在,如果不存在則會拋出異常(但可以在之前添加一個 if 元素)。
<?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>
                    
include 元素
include 元素是文檔中使用最頻繁的元素之一,它將指定的 xsql 元素編譯並添加到當前位置(注,是當前位置!)。關於示例,上面的內容已經有包含了。
trim 元素
trim 元素一般用於 where 語句和 set 語句,該元素如果在存在內容時,添加 prefix 屬性值,並且自動過濾頭尾 trim 屬性存在的值。具體示例查看 if-else 元素
parameters 元素
parameters 元素一般用於自定義參數的情景。例如:ORACLE 插入序列時,需要訪問 seq_xxx.nextval 來獲取參數序列,並且希望整體 xsql 編譯並執行了以后,調用方可以獲取到該參數。parameters 可以包含兩個子元素:expression 和 xsql 元素。
  <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>
                    
X-SQL 的實踐

我整理了 12 個實例。你可以通過以下 gif 小動畫來查看效果。

X-SQL 的實踐

雖然我只進行了簡單的測試,但 X-SQL 的性能還是十分理想的。在 foreach(該元素最消耗性能) 2000 次測試下,采用緩存是 200~300 毫秒,非緩存效果下 500~600 毫秒(非最佳效果,采用 release,但是在VS 運行測試,而不是直接打開)。

在大多數情況下,我們建議采用 names(根據參數集合長度、參數名進行緩存)以及 forever(永久緩存)。而不建議使用 values(根據參數值緩存)。

其實不一定要采用緩存,通過上面的數據可以看出,緩存的后的性能只有一倍之差(緩存的性能損耗:Dict.TryGetValue,可見 X-SQL 的高性能),如果在非硬性要求下,除了 forever 緩存,其他緩存均不推薦。

最后

壓力測試的必要性還是有的,能否經歷的起大項目的高頻率、多線程調用,我只能表示“我有信心”。

目前我將主要精力集中在:如何實現功能。對於性能等其他的要求,我只能延后。

關於源碼

我很猶豫要不要開放出去,其實從之前的文章大家可以看得出,我秉着開源原則,所以開源是必然,但文章發表后為什么不放上源碼,主要有兩個考慮:

  • 你需要這份源碼嗎?
  • 你會為這份源碼提出建議嗎?

如果有興趣的朋友很多的話,我會開放到 CodePlex。

最后,對博友 blue1000hpze2000 以及其他朋友說一聲抱歉,由於最近公司正在着手新產品的研發,我負責的文檔太多了,所以一直耽擱下來了。

但我很高興地告訴你們,關於 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 版功能已完成。

我寫的很用心,如果您對這篇文章感興趣的話,請推薦……感謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM