Mybatis的幾點小結

jopen 11年前發布 | 80K 次閱讀 MyBatis3 持久層框架 MyBatis

1.Mapper層參數為Map,由Service層負責重載。

    Mapper由于機制的問題,不能重載,參數一般設置成Map,但這樣會使參數變得模糊,如果想要使代碼變得清晰,可以通過service層來實現重載的目的,對外提供的Service層是重載的,但這些重載的Service方法其實是調同一個Mapper,只不過相應的參數并不一致。

    也許有人會想,為什么不在Service層也設置成Map呢?我個人是不推薦這么做的,雖然為了方便,我在之前的項目中也大量采用了這種方式,但很明顯會給日后的維護工作帶來麻煩。因為這么做會使你整個MVC都依賴于Map模型,這個模型其實是很不錯的,方便搭框架,但存在一個問題:僅僅看方法簽名,你不清楚Map中所擁有的參數個數、類型、每個參數代表的含義。

    試想,你只對Service層變更,或者DAO層變更,你需要清楚整個流程中Map傳遞過來的參數,除非你注釋或者文檔良好,否則必須把每一層的代碼都了解清楚,你才知道傳遞了哪些參數。針對于簡單MVC,那倒也還好,但如果層次復雜之后,代碼會變得異常復雜,而且如果我增加一個參數,需要把每一個層的注釋都添加上。相對于注釋,使用方法簽名來保證這種代碼可控性會來得更可行一些,因為注釋有可能是過時的,但方法簽名一般不太可能是陳舊的。

2.盡量少用if choose等語句,降低維護的難度。

    Mybatis的配置SQL時,盡量少用if choose 等標簽,能用SQL實現判斷的盡量用SQL來判斷(CASE WHEN ,DECODE等),以便后期維護。否則,一旦SQL膨脹,超級惡心,如果需要調試Mybatis中的SQL,需要去除大量的判斷語句,非常麻煩。另一方面,大量的if判斷,會使生成的SQL中包含大量的空格,增加網絡傳輸的時間,也不可取。

    而且大量的if choose語句,不可避免地,每次生成的SQL會不太一致,會導致ORACLE大量的硬解析,也不可取。
我們來看看這樣的SQL:

SELECT * FROM T_NEWS_TEXT WHERE 1 = 1
< choose>
< if test ="startdate != null and startdate != '' and enddate != null and endate != ''">
AND PUBLISHTIME >= #{startdate} AND PUBLISHTIME <= #{enddate}
</ if>
<otherwise>
AND PUBLISHTIME >= SYSDATE - 7 AND PUBLISHTIME <= SYSDATE
</otherwise></ choose >
    這樣的if判斷,其實是完全沒有必要的,我們可以很簡單的采用DECODE來解決默認值問題:

SELECT * FROM T_NEWS_TEXT WHERE PUBLISHTIME >= DECODE(#{startdate},NULL,SYSDATE-7, #{startdate}) AND PUBLISHTIME <= DECODE(#{enddate},NULL,SYSDATE,#{enddate})

    當然有人會想,引入CASE WHEN,DECODE會導致需要ORACLE函數解析,會拖慢SQL執行時間,有興趣的同學可以回去做一下測試,看看是否會有大的影響。就個人經驗而言,在我的開發過程,沒有發現因為函數解析導致SQL變慢的情形。影響SQL執行效率的一般情況下是JOIN、ORDER BY、DISTINCT、PARTITATION BY等這些操作,這些操作一般與表結構設計有很大的關聯。相對于這些的效率影響程度,函數解析對于SQL執行速度影響應該是可以忽略不計的。

    當然這并不是絕對的,有時候為了優化SQL,不得不使用if來解決,比如說LIKE語句,當然一般不推薦使用LIKE,但如果存在使用的場景,盡可能在不需要使用時候去除LIKE,比如查詢文章標題,以提高查詢效率。 最好的方式是使用lucence等搜索引擎來解決這種全文索引的問題。

    關于LIKE,我這邊還有個案例,雖然不是關于if判斷的,但也是非常值得注意的地方:比如一些樹型菜單,節點會設計成'01','0101',用兩位節點來區分層級,這時候,如果需要查詢01節點下所有的節點,最簡單的SQL便是:SELECT * FROM TREE WHERE ID LIKE '01%',這種SQL其實無可厚非,因為它也能用到索引,所以不需要特別的處理,直接使用就行了。但是要注意使用的方式: ID LIKE #{ID} || '%'而不是ID LIKE '${ID}%',減少硬解析的可能。

    有人覺得使用||會增加ORACLE處理的時間,我覺得不要把ORACLE看得太傻,雖然有時候確實非常傻,有空可以再總結ORACLE傻不垃圾的地方,但是稍加測試便知:這種串聯方式,對于整個SQL的解析執行,應該是微乎其微的。

    總的來說,if與choose判斷分支是不可能完全去除的,但是推薦使用SQL原生的方式來解決一些動態問題,而不應該完全依賴Mybatis來完成動態分支的判斷,因為判斷分支過于復雜,而且難以維護。

3.用XML注釋取代SQL注釋。

    Mybatis中原SQL的注釋盡量不要保留,注釋會引發一些問題,如果需要使用注釋,可以在XML中用<!-- -->來注釋,保證在生成的SQL中不會存在SQL注釋,從而降低問題出現的可能性。這樣做還有一個好處,就是在IDE中可以很清楚的區分注釋與SQL。

    現在來談談注釋引發的問題,我做的一個項目中,分頁組件是基于Mybatis的,它會在你寫的SQL腳本外面再套一層SELECT COUNT() ROWNUM_ FROM (....) 計算總記錄數,同時有另一個嵌套SELECT FROM(...) WHERE ROWNUM > 10 AND RONNUM < 10 * 2這種方式生成分頁信息,如果你的腳本中最后一行出現了注釋,則添加的部分會成為注釋的一部分,執行就會報錯。除此之外,某些情況下也可能導致部分條件被忽略,如下面的情況:

SELECT * FROM TEST WHERE COL1 >  1 -- 這里是注釋<if test="a != null and a != ''">AND COL2 = #{a}</if>
    即使傳入的參數中存在對應的參數,實際也不會產生效果,因為后面的內容實際上是被完全注釋了。這種錯誤,如果不經過嚴格的測試,是很難發現的。一般情況下,XML注釋完全可以替代SQL注釋,因此這種行為應該可以禁止掉。

4.盡可能使用#{},而不是${}.

    Mybatis中盡量不要使用${},盡量這樣做很方便開發,但是有一個問題,就是大量使用會導致ORACLE的硬解析,拖慢數據庫性能,運行越久,數據庫性能會越差。對于一般多個字符串IN的處理,可以參考如下的解決方案:http://www.myexception.cn/sql/849573.html,基本可以解決大部分${}.

    當然還有一些特殊情況是沒有辦法處理的,比如說動態注入列名、表名等。對于這些情況,則比較棘手,沒有找到比較方便的手段。由于這種情況出現的可能性會比較少,所以使用${}倒也不至于有什么太大的影響。當然你如果有代碼潔癖的話,可以使用ORACLE的動態執行SQL的機制Execute immediate,這樣就可以完全避免${}出現的可能性了。這樣會引入比較復雜的模型,這個時候,你就需要取舍了。

    針對于以上動態SQL所導致的問題,最激進的方式是全部采用存儲過程,用數據庫原生的方式來解決,方便開發調試,當然也會帶來問題:對開發人員會有更高的要求、存儲過程的管理等等,我這邊項目沒有采用過這種方式,這里不做更多的展開。

5.簡單使用Mybatis。

    Mybatis的功能相對而言還是比較弱的,缺少了好多必要的輔助庫,字符串處理等等,擴展也比較困難,一般也就可能對返回值進行一些處理。因此最好僅僅把它作為單純的SQL配置文件,以及簡單的ORM框架。不要嘗試在Mybatis中做過多的動態SQL,否則會導致后續的維護非常惡心。


 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!