簡介:什么是MyBatis?
(前身為iBatis) MyBatis 是一個可以自定義SQL、存儲過程和高級映射的持久層框架。MyBatis消除了幾乎所有的JDBC代碼和參數的手工設置以及對結果集的檢索。MyBatis可以使用簡單的XML或注解用于配置和原始映射,將接口和Java的POJO(Plain Old Java Objects,普通的Java對象)映射成數據庫中的記錄。
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:
1
2
3
4
5
6
7
8
|
<code class= "hljs sql" style= "padding:0.5em; margin:0px; display:block; color:rgb(101,123,131); overflow-x:auto; background:rgb(253,246,227)" ><span class= "hljs-operator" style= "padding:0px; margin:0px" ><span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > SELECT </span> * <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > FROM </span> T_NEWS_TEXT <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > WHERE </span> <span class= "hljs-number" style= "padding:0px; margin:0px; color:rgb(42,161,152)" >1</span> = <span class= "hljs-number" style= "padding:0px; margin:0px; color:rgb(42,161,152)" >1</span> < <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" >choose</span>> < <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" >if</span> test =<span class= "hljs-string" style= "padding:0px; margin:0px; color:rgb(42,161,152)" > "startdate != null and startdate != '' and enddate != null and endate != ''" </span>> <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > AND </span> PUBLISHTIME >= #{startdate} <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > AND </span> PUBLISHTIME <= #{enddate} </ <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" >if</span>> <otherwise> <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > AND </span> PUBLISHTIME >= <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" >SYSDATE</span> - <span class= "hljs-number" style= "padding:0px; margin:0px; color:rgb(42,161,152)" >7</span> <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > AND </span> PUBLISHTIME <= <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" >SYSDATE</span> </otherwise></ <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" >choose</span> ></span> |
這樣的if判斷,其實是完全沒有必要的,我們可以很簡單的采用DECODE來解決默認值問題:
1
|
<code class= "hljs sql" style= "padding:0.5em; margin:0px; display:block; color:rgb(101,123,131); overflow-x:auto; background:rgb(253,246,227)" ><span class= "hljs-operator" style= "padding:0px; margin:0px" ><span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > SELECT </span> * <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > FROM </span> T_NEWS_TEXT <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > WHERE </span> PUBLISHTIME >= <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" >DECODE</span>(#{startdate},<span class= "hljs-literal" style= "padding:0px; margin:0px" > NULL </span>,<span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" >SYSDATE</span>-<span class= "hljs-number" style= "padding:0px; margin:0px; color:rgb(42,161,152)" >7</span>, #{startdate}) <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > AND </span> PUBLISHTIME <= <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" >DECODE</span>(#{enddate},<span class= "hljs-literal" style= "padding:0px; margin:0px" > NULL </span>,<span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" >SYSDATE</span>,#{enddate})</span></code> |
當然有人會想,引入CASE WHEN,DECODE會導致需要ORACLE函數解析,會拖慢SQL執行時間,有興趣的同學可以回去做一下測試,看看是否會有大的影響。就個人經驗而言,在我的開發過程,沒有發現因為函數解析導致SQL變慢的情形。影響SQL執行效率的一般情況下是JOIN、ORDER BY、DISTINCT、PARTITATION BY等這些操作,這些操作一般與表結構設計有很大的關聯。相對于這些的效率影響程度,函數解析對于SQL執行速度影響應該是可以忽略不計的。
另外一點,對于一些默認值的賦值,像上面那條SQL,默認成當前日期什么的,其實可以完全提到Service層或Controller層做處理,在Mybatis中應該要少用這些判斷。因為,這樣的話,很難做緩存處理。如果startdate為空,在SQL上使用動態的SYSDATE,就無法確定緩存startdate日期的key應該是什么了。所以參數最好在傳遞至Mybatis之前都處理好,這樣Mybatis層也能減少部分if choose語句,同時也方便做緩存處理。
當然不使用if choose也并不是絕對的,有時候為了優化SQL,不得不使用if來解決,比如說LIKE語句,當然一般不推薦使用LIKE,但如果存在使用的場景,盡可能在不需要使用時候去除LIKE,比如查詢文章標題,以提高查詢效率。 最好的方式是使用lucence等搜索引擎來解決這種全文索引的問題。
總的來說,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這種方式生成分頁信息,如果你的腳本中最后一行出現了注釋,則添加的部分會成為注釋的一部分,執行就會報錯。除此之外,某些情況下也可能導致部分條件被忽略,如下面的情況:
1
|
<code class= "hljs vbnet" style= "padding:0.5em; margin:0px; display:block; color:rgb(101,123,131); overflow-x:auto; background:rgb(253,246,227)" ><span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > SELECT </span> * <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > FROM </span> TEST <span class= "hljs-keyword" style= "padding:0px; margin:0px; color:rgb(133,153,0)" > WHERE </span> COL1 > <span class= "hljs-number" style= "padding:0px; margin:0px; color:rgb(42,161,152)" >1</span> -- 這里是注釋<<span class="hljs-keyword" style="padding:0px; margin:0px; color:rgb(133,153,0)">if</span> test=<span class="hljs-string" style="padding:0px; margin:0px; color:rgb(42,161,152)">"a != null and a != ''"</span>><span class="hljs-keyword" style="padding:0px; margin:0px; color:rgb(133,153,0)">AND</span> COL2 = <span class="hljs-preprocessor" style="padding:0px; margin:0px; color:rgb(203,75,22)">#{a}</<span class="hljs-keyword" style="padding:0px; margin:0px">if</span>></span></code> |
即使傳入的參數中存在對應的參數,實際也不會產生效果,因為后面的內容實際上是被完全注釋了。這種錯誤,如果不經過嚴格的測試,是很難發現的。一般情況下,XML注釋完全可以替代SQL注釋,因此這種行為應該可以禁止掉。
4.盡可能使用#{},而不是${}.
Mybatis中盡量不要使用${},盡量這樣做很方便開發,但是有一個問題,就是大量使用會導致ORACLE的硬解析,拖慢數據庫性能,運行越久,數據庫性能會越差。對于一般多個字符串IN的處理,可以參考如下的解決方案:http://www.myexception.cn/sql/849573.html,基本可以解決大部分${}.
關于${},另一個誤用的地方就是LIKE,我這邊還有個案例:比如一些樹型菜單,節點會設計成'01','0101',用兩位節點來區分層級,這時候,如果需要查詢01節點下所有的節點,最簡單的SQL便是:SELECT * FROM TREE WHERE ID LIKE '01%',這種SQL其實無可厚非,因為它也能用到索引,所以不需要特別的處理,直接使用就行了。但如果是文章標題,則需要額外注意了:SELECT * FROM T_NEWS_TEXT WHERE TITLE LIKE '%OSC%',這是怎么也不會用到索引的,上面說了,最好采用全文檢索。但如果離不開LIKE,就需要注意使用的方式: ID LIKE #{ID} || '%'而不是ID LIKE '${ID}%',減少硬解析的可能。
有人覺得使用||會增加ORACLE處理的時間,我覺得不要把ORACLE看得太傻,雖然有時候確實非常傻,有空可以再總結ORACLE傻不垃圾的地方,但是稍加測試便知:這種串聯方式,對于整個SQL的解析執行,應該是微乎其微的。
當然還有一些特殊情況是沒有辦法處理的,比如說動態注入列名、表名等。對于這些情況,則比較棘手,沒有找到比較方便的手段。由于這種情況出現的可能性會比較少,所以使用${}倒也不至于有什么太大的影響。當然你如果有代碼潔癖的話,可以使用ORACLE的動態執行SQL的機制Execute immediate,這樣就可以完全避免${}出現的可能性了。這樣會引入比較復雜的模型,這個時候,你就需要取舍了。
針對于以上動態SQL所導致的問題,最激進的方式是全部采用存儲過程,用數據庫原生的方式來解決,方便開發調試,當然也會帶來問題:對開發人員會有更高的要求、存儲過程的管理等等,我這邊項目沒有采用過這種方式,這里不做更多的展開。
5.簡單使用Mybatis。
Mybatis的功能相對而言還是比較弱的,缺少了好多必要的輔助庫,字符串處理等等,擴展也比較困難,一般也就可能對返回值進行一些處理。因此最好僅僅把它作為單純的SQL配置文件,以及簡單的ORM框架。不要嘗試在Mybatis中做過多的動態SQL,否則會導致后續的維護非常惡心。
以上所述是小編給大家介紹的Mybatis學習總結之mybatis使用建議,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!