簡介:什么是MyBatis?
(前身為iBatis) MyBatis 是一個(gè)可以自定義SQL、存儲(chǔ)過程和高級(jí)映射的持久層框架。MyBatis消除了幾乎所有的JDBC代碼和參數(shù)的手工設(shè)置以及對(duì)結(jié)果集的檢索。MyBatis可以使用簡單的XML或注解用于配置和原始映射,將接口和Java的POJO(Plain Old Java Objects,普通的Java對(duì)象)映射成數(shù)據(jù)庫中的記錄。
1.Mapper層參數(shù)為Map,由Service層負(fù)責(zé)重載。
Mapper由于機(jī)制的問題,不能重載,參數(shù)一般設(shè)置成Map,但這樣會(huì)使參數(shù)變得模糊,如果想要使代碼變得清晰,可以通過service層來實(shí)現(xiàn)重載的目的,對(duì)外提供的Service層是重載的,但這些重載的Service方法其實(shí)是調(diào)同一個(gè)Mapper,只不過相應(yīng)的參數(shù)并不一致。
也許有人會(huì)想,為什么不在Service層也設(shè)置成Map呢?我個(gè)人是不推薦這么做的,雖然為了方便,我在之前的項(xiàng)目中也大量采用了這種方式,但很明顯會(huì)給日后的維護(hù)工作帶來麻煩。因?yàn)檫@么做會(huì)使你整個(gè)MVC都依賴于Map模型,這個(gè)模型其實(shí)是很不錯(cuò)的,方便搭框架,但存在一個(gè)問題:僅僅看方法簽名,你不清楚Map中所擁有的參數(shù)個(gè)數(shù)、類型、每個(gè)參數(shù)代表的含義。
試想,你只對(duì)Service層變更,或者DAO層變更,你需要清楚整個(gè)流程中Map傳遞過來的參數(shù),除非你注釋或者文檔良好,否則必須把每一層的代碼都了解清楚,你才知道傳遞了哪些參數(shù)。針對(duì)于簡單MVC,那倒也還好,但如果層次復(fù)雜之后,代碼會(huì)變得異常復(fù)雜,而且如果我增加一個(gè)參數(shù),需要把每一個(gè)層的注釋都添加上。相對(duì)于注釋,使用方法簽名來保證這種代碼可控性會(huì)來得更可行一些,因?yàn)樽⑨層锌赡苁沁^時(shí)的,但方法簽名一般不太可能是陳舊的。
2.盡量少用if choose等語句,降低維護(hù)的難度。
Mybatis的配置SQL時(shí),盡量少用if choose 等標(biāo)簽,能用SQL實(shí)現(xiàn)判斷的盡量用SQL來判斷(CASE WHEN ,DECODE等),以便后期維護(hù)。否則,一旦SQL膨脹,超級(jí)惡心,如果需要調(diào)試Mybatis中的SQL,需要去除大量的判斷語句,非常麻煩。另一方面,大量的if判斷,會(huì)使生成的SQL中包含大量的空格,增加網(wǎng)絡(luò)傳輸?shù)臅r(shí)間,也不可取。
而且大量的if choose語句,不可避免地,每次生成的SQL會(huì)不太一致,會(huì)導(dǎo)致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判斷,其實(shí)是完全沒有必要的,我們可以很簡單的采用DECODE來解決默認(rèn)值問題:
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> |
當(dāng)然有人會(huì)想,引入CASE WHEN,DECODE會(huì)導(dǎo)致需要ORACLE函數(shù)解析,會(huì)拖慢SQL執(zhí)行時(shí)間,有興趣的同學(xué)可以回去做一下測試,看看是否會(huì)有大的影響。就個(gè)人經(jīng)驗(yàn)而言,在我的開發(fā)過程,沒有發(fā)現(xiàn)因?yàn)楹瘮?shù)解析導(dǎo)致SQL變慢的情形。影響SQL執(zhí)行效率的一般情況下是JOIN、ORDER BY、DISTINCT、PARTITATION BY等這些操作,這些操作一般與表結(jié)構(gòu)設(shè)計(jì)有很大的關(guān)聯(lián)。相對(duì)于這些的效率影響程度,函數(shù)解析對(duì)于SQL執(zhí)行速度影響應(yīng)該是可以忽略不計(jì)的。
另外一點(diǎn),對(duì)于一些默認(rèn)值的賦值,像上面那條SQL,默認(rèn)成當(dāng)前日期什么的,其實(shí)可以完全提到Service層或Controller層做處理,在Mybatis中應(yīng)該要少用這些判斷。因?yàn)椋@樣的話,很難做緩存處理。如果startdate為空,在SQL上使用動(dòng)態(tài)的SYSDATE,就無法確定緩存startdate日期的key應(yīng)該是什么了。所以參數(shù)最好在傳遞至Mybatis之前都處理好,這樣Mybatis層也能減少部分if choose語句,同時(shí)也方便做緩存處理。
當(dāng)然不使用if choose也并不是絕對(duì)的,有時(shí)候?yàn)榱藘?yōu)化SQL,不得不使用if來解決,比如說LIKE語句,當(dāng)然一般不推薦使用LIKE,但如果存在使用的場景,盡可能在不需要使用時(shí)候去除LIKE,比如查詢文章標(biāo)題,以提高查詢效率。 最好的方式是使用lucence等搜索引擎來解決這種全文索引的問題。
總的來說,if與choose判斷分支是不可能完全去除的,但是推薦使用SQL原生的方式來解決一些動(dòng)態(tài)問題,而不應(yīng)該完全依賴Mybatis來完成動(dòng)態(tài)分支的判斷,因?yàn)榕袛喾种н^于復(fù)雜,而且難以維護(hù)。
3.用XML注釋取代SQL注釋。
Mybatis中原SQL的注釋盡量不要保留,注釋會(huì)引發(fā)一些問題,如果需要使用注釋,可以在XML中用<!-- -->來注釋,保證在生成的SQL中不會(huì)存在SQL注釋,從而降低問題出現(xiàn)的可能性。這樣做還有一個(gè)好處,就是在IDE中可以很清楚的區(qū)分注釋與SQL。
現(xiàn)在來談?wù)勛⑨屢l(fā)的問題,我做的一個(gè)項(xiàng)目中,分頁組件是基于Mybatis的,它會(huì)在你寫的SQL腳本外面再套一層SELECT COUNT(*) ROWNUM_ FROM (....) 計(jì)算總記錄數(shù),同時(shí)有另一個(gè)嵌套SELECT * FROM(...) WHERE ROWNUM > 10 AND RONNUM < 10 * 2這種方式生成分頁信息,如果你的腳本中最后一行出現(xiàn)了注釋,則添加的部分會(huì)成為注釋的一部分,執(zhí)行就會(huì)報(bào)錯(cuò)。除此之外,某些情況下也可能導(dǎo)致部分條件被忽略,如下面的情況:
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> |
即使傳入的參數(shù)中存在對(duì)應(yīng)的參數(shù),實(shí)際也不會(huì)產(chǎn)生效果,因?yàn)楹竺娴膬?nèi)容實(shí)際上是被完全注釋了。這種錯(cuò)誤,如果不經(jīng)過嚴(yán)格的測試,是很難發(fā)現(xiàn)的。一般情況下,XML注釋完全可以替代SQL注釋,因此這種行為應(yīng)該可以禁止掉。
4.盡可能使用#{},而不是${}.
Mybatis中盡量不要使用${},盡量這樣做很方便開發(fā),但是有一個(gè)問題,就是大量使用會(huì)導(dǎo)致ORACLE的硬解析,拖慢數(shù)據(jù)庫性能,運(yùn)行越久,數(shù)據(jù)庫性能會(huì)越差。對(duì)于一般多個(gè)字符串IN的處理,可以參考如下的解決方案:http://www.myexception.cn/sql/849573.html,基本可以解決大部分${}.
關(guān)于${},另一個(gè)誤用的地方就是LIKE,我這邊還有個(gè)案例:比如一些樹型菜單,節(jié)點(diǎn)會(huì)設(shè)計(jì)成'01','0101',用兩位節(jié)點(diǎn)來區(qū)分層級(jí),這時(shí)候,如果需要查詢01節(jié)點(diǎn)下所有的節(jié)點(diǎn),最簡單的SQL便是:SELECT * FROM TREE WHERE ID LIKE '01%',這種SQL其實(shí)無可厚非,因?yàn)樗材苡玫剿饕圆恍枰貏e的處理,直接使用就行了。但如果是文章標(biāo)題,則需要額外注意了:SELECT * FROM T_NEWS_TEXT WHERE TITLE LIKE '%OSC%',這是怎么也不會(huì)用到索引的,上面說了,最好采用全文檢索。但如果離不開LIKE,就需要注意使用的方式: ID LIKE #{ID} || '%'而不是ID LIKE '${ID}%',減少硬解析的可能。
有人覺得使用||會(huì)增加ORACLE處理的時(shí)間,我覺得不要把ORACLE看得太傻,雖然有時(shí)候確實(shí)非常傻,有空可以再總結(jié)ORACLE傻不垃圾的地方,但是稍加測試便知:這種串聯(lián)方式,對(duì)于整個(gè)SQL的解析執(zhí)行,應(yīng)該是微乎其微的。
當(dāng)然還有一些特殊情況是沒有辦法處理的,比如說動(dòng)態(tài)注入列名、表名等。對(duì)于這些情況,則比較棘手,沒有找到比較方便的手段。由于這種情況出現(xiàn)的可能性會(huì)比較少,所以使用${}倒也不至于有什么太大的影響。當(dāng)然你如果有代碼潔癖的話,可以使用ORACLE的動(dòng)態(tài)執(zhí)行SQL的機(jī)制Execute immediate,這樣就可以完全避免${}出現(xiàn)的可能性了。這樣會(huì)引入比較復(fù)雜的模型,這個(gè)時(shí)候,你就需要取舍了。
針對(duì)于以上動(dòng)態(tài)SQL所導(dǎo)致的問題,最激進(jìn)的方式是全部采用存儲(chǔ)過程,用數(shù)據(jù)庫原生的方式來解決,方便開發(fā)調(diào)試,當(dāng)然也會(huì)帶來問題:對(duì)開發(fā)人員會(huì)有更高的要求、存儲(chǔ)過程的管理等等,我這邊項(xiàng)目沒有采用過這種方式,這里不做更多的展開。
5.簡單使用Mybatis。
Mybatis的功能相對(duì)而言還是比較弱的,缺少了好多必要的輔助庫,字符串處理等等,擴(kuò)展也比較困難,一般也就可能對(duì)返回值進(jìn)行一些處理。因此最好僅僅把它作為單純的SQL配置文件,以及簡單的ORM框架。不要嘗試在Mybatis中做過多的動(dòng)態(tài)SQL,否則會(huì)導(dǎo)致后續(xù)的維護(hù)非常惡心。
以上所述是小編給大家介紹的Mybatis學(xué)習(xí)總結(jié)之mybatis使用建議,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)服務(wù)器之家網(wǎng)站的支持!