程序員,Mybatis 你踩過坑嗎?

  • 案例一:
  • 案例二:
  • 案例三:
  • 案例四:
  • 案例五:
  • 案例六:
  • 案例七:
  • 優缺點
  • 優點:
  • 缺點:
  • 總結:

大多數開發者應該都使用過Hibernate或者Mybatis的框架,或多或少都踩過一些坑!

如在MyBatis/Ibatis中#和$的區別,#方式能夠很大程度防止sql注入,$方式無法防止Sql注入。所以,老司機 對新手說,最好用#。簡單的說#{}是經過預編譯的,是安全的,而

程序員,Mybatis 你踩過坑嗎?

解決一些實際問題。

如在執行sql語句時你有時並不希望讓變量進行處理,而是直接賦值執行,這時就要用到(${a})了,在使用時還要這樣賦值 @Param(value="a") String a

如日期問題:

可能會遇到日期格式的時間段問題,當數據庫的時間為DATE類型時,MyBatis的jdbcType應該使用DATE

jdbcType=DATE,而不是使用jdbcType=TIMESTAMP

如在使用resultMap的時候,要把ID寫在第一行,否則的話,就會報錯。

又如最近在做的項目,遇到myBatis的大坑,Mybatis一直報異常Java.lang.ArrayIndexOutOfBoundsException,於是開始代碼查錯,代碼中有存儲過程,然後開發使用ROOT用戶執行SQL跑出來的數據結果集是正常的,在測試環境程序運行也正常,但是在正式環境就其他用戶不行,最後發現是因為數據庫沒有給該用戶授權出了問題。

案例一:

作為新手,在此記下剛踩的一個坑,(踩踩更健康= =踩過痛過才不會再次錯),寫了一個sql語句用到兩張表,兩張表中有兩個字段名字是一樣的都是Time和Content,然後要查詢這兩張表的這兩個字段都要查出來放到一個dto中,dto如下圖所示,

程序員,Mybatis 你踩過坑嗎?

sql語句如下,

程序員,Mybatis 你踩過坑嗎?

然而運行後卻發現後幾個在數據庫表裡同名的字段取出來都是null,

程序員,Mybatis 你踩過坑嗎?

但是放到數據庫那邊執行是沒有取出空數據的,

程序員,Mybatis 你踩過坑嗎?

真是苦惱= =,後來經大神指點,sql語句查詢出來的這個字段名必須和dto的參數名一致,改成這樣就通過了,

程序員,Mybatis 你踩過坑嗎?

數據都取出來了。。。。。。。。。。還記得在hibernate裡用hql時放到dto裡,select new dto名()參數順序和類型一致就可以取出來。。。這應該算一個不同點吧,,感覺還是hql用起來舒服,,,求大神科普兩者的差別優缺點

當實體類中的屬性名和表中的字段名不一致時,使用MyBatis進行查詢操作時無法查詢出相應的結果的問題,當時上網查了很多才知道,看到的一個解決方法分享給大家,通過來映射字段名和實體類屬性名的一一對應關係。這種方式是使用MyBatis提供的解決方式來解決字段名和屬性名的映射關係的!

案例二:

數據庫表使用了聯合主鍵,逆向生成的時候生成了兩個實體類。看起來彆扭。但還是可以用。後來就先取消主鍵,生成完後再將主鍵加上。還有就是,tinyint本來以為用來表示比較小的整數,結果生成了布爾型的屬性。後來就表示是和否才用tinyint了。逆向生成的sql語句絕對不能人為改動,否則再次生成的時候會重複生成。但是,儘管踩過坑,我還是覺得mybatis超級好用,比hibernate好多了。雖然hibernate我只試過一點之後就完全轉向了mybatis了。

案例三:

sum()和count()使用場景不對導致出錯:

count()、count(1)、count(0)就是指絕對的行數,哪怕某行所有字段全部為null也會計算在內。count(1)和count()相比,innodb來說count(*)效率低。

如果count(列名)查詢出來的結果就是查出列名中不為null的行數;

sum(列名)對指定列名進行求和

MyBatis把int類型的0處理成空串’’和mysql處理空串’’為0的問題,在Mybatis的Mapper中整數類型條件該如何判斷?

當數據庫字段類型是整數,如果參數變量為空字符串或者NULL,Mybatis會自動將參數賦值0,所以如果要判斷整數參數的多種狀態在傳遞數值到Mapper之前就要判斷是否為空字符串和NULL並將相應的狀態數值賦值給該參數,否則參數值等於空字符串、NULL和0得到的結果是一樣的。

一般情況下,涉及到int類型的操作的時候,在Service中會統一把數字類型先變成字符串類型,然後再傳遞到Mapper中操作。

時間戳的使用

在創建新記錄的時候把這個字段設置為當前時間,但以後修改時,不再刷新它(可以給createtime使用這個):TIMESTAMP DEFAULT CURRENT_TIMESTAMP

在創建新記錄和修改現有記錄的時候都對這個數據列刷新(可以給update使用這個):

TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

在使用resultMap的時候,要把ID寫在第一行,否則的話,就會報錯。

案例四:

XML轉義字符,如果直接寫就會報錯,需要用左邊一列的轉義字符

< < 小於號 > > 大於號

& & 和

' ' 單引號

" " 雙引號

案例五:

前幾天在項目中碰到,來說下吧。大神可繞道。在使用selectOne查詢個數時,

如果你寫了resultType為Integer,然後在業務代碼中很自然的用一個變量int去接當前這個方法的返回,如果按照你傳入的條件在數據庫中沒有找到相關的值,此時selectOne方法的返回值會是一個null,當你使用Java的自動拆箱機制的時候會報出一個無情的NPE。

原因:Java在自動拆箱的時候會調用Integer類中的intValue方法,如果當前對象為null,則拋出NPE。

因此,在接受的時候要判空,否則可能異常。

案例六:

多參數的使用

MyBatis的查詢或者更新中,如果需要多個參數有如下幾種辦法:

對象映射,建立一個Java對象,並作為接口的參數,對象的屬性可以直接使用#{屬性名}的方式訪問;

Map, 參數為一個Map, key對於屬性名,value對於參數值,這個方法就是傳參數是需要建立一個Map的臨時對象

@param參數註解,在接口方法參數前加入參數名稱註解,這樣就可以直接在Mapper中通過參數名訪問

通過序號訪問,第一個參數#{0}或#{param1}, 第二個參數#{1}, #{param2}

MyBatis中時間字段的使用–返回

時間字段的返回目前筆者採用放回字符串的方式:

date_format(update_time, ‘%Y-%c-%d %H:%i:%s’) updatetime

採用MySQL的時間格式化方法。

或者放回Timestamp類型的數據,要求放回對象屬性參數為Timestamp.

MyBatis中時間字段的使用–參數

如果需要查詢一段時間範圍的數據時,可以通過以下動態SQL的方式查詢數據:

and lbr.update_time > #{startTime}

and lbr.update_time < #{endTime, javaType=Date, jdbcType=TIMESTAMP}

對於的接口方法名稱如下:

… Date startTime, Date endTime…

我想這個方法會比通過格式轉換的效率要高一些

  1. MyBatis中時間字段的使用–寫入
  2. 寫入可是直接寫入Timestamp的數據,需要描述一些寫入的jdbcType,如下:

{installTime, jdbcType=TIMESTAMP}

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
< if test ="startdate != null and startdate != '' and enddate != null and endate != ''">
AND PUBLISHTIME >= #{startdate} AND PUBLISHTIME <= #{enddate}
AND PUBLISHTIME >= SYSDATE - 7 AND PUBLISHTIME <= SYSDATE

這樣的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,默認成當前日期什麼的,其實可以完全提到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這種方式生成分頁信息,如果你的腳本中最後一行出現了註釋,則添加的部分會成為註釋的一部分,執行就會報錯。除此之外,某些情況下也可能導致部分條件被忽略,如下面的情況:

SELECT * FROM TEST
WHERE COL1 > 1 -- 這裡是註釋AND COL2 = #{a}

即使傳入的參數中存在對應的參數,實際也不會產生效果,因為後面的內容實際上是被完全註釋了。這種錯誤,如果不經過嚴格的測試,是很難發現的。一般情況下,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 '

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

當然還有一些特殊情況是沒有辦法處理的,比如說動態注入列名、表名等。對於這些情況,則比較棘手,沒有找到比較方便的手段。由於這種情況出現的可能性會比較少,所以使用ID有人覺得使用∣∣會增加ORACLE處理的時間,我覺得不要把ORACLE看得太傻,雖然有時候確實非常傻,有空可以再總結ORACLE傻不垃圾的地方,但是稍加測試便知:這種串聯方式,對於整個SQL的解析執行,應該是微乎其微的。當然還有一些特殊情況是沒有辦法處理的,比如說動態注入列名、表名等。對於這些情況,則比較棘手,沒有找到比較方便的手段。由於這種情況出現的可能性會比較少,所以使用{}倒也不至於有什麼太大的影響。當然你如果有代碼潔癖的話,可以使用ORACLE的動態執行SQL的機制Execute immediate,這樣就可以完全避免${}出現的可能性了。這樣會引入比較複雜的模型,這個時候,你就需要取捨了。

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

5.簡單使用Mybatis。

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

幾點技巧總結;

1、查詢很多字段時可以提出來再引入到sql語句

提取:

id, type, shopCouId, Path, fromDate, toDate, insDate, insUserId, updDate, updUserId, delFlg

引入:

select

from adinfo

where id = #{id,jdbcType=INTEGER}

2、如果sql語句中需要使用, "" 符號時,需要使用< > " 或者

 CDATA內部所有東西都會被解析器忽略 

select type, shopCouId, Path
from adinfo
WHERE delFlg ='0'
and fromDate < #{date} and toDate >= #{date}

3、緩存使用

在增刪查改時,可以使用緩存屬性控制數據緩存

4、可以判斷傳進來的參數,再進行操作

and langCd = #{langCd,jdbcType=VARCHAR}

5、可以在sql語句中直接進行加減乘除計算,模糊查詢時,需要注意使用方式

SELECT sum(b.netCnt) + #{netCnt,jdbcType=INTEGER}
FROM collectcnt b
WHERE b.shopId = #{shopId,jdbcType=INTEGER}
AND b.delflg = '0'
newCnt = newCnt + 1,
netCnt = netCnt +1,
sumCnt = sumCnt + 1,
AND o.oTime LIKE CONCAT('%',#{sdate},'%')

MyBatis把int類型的0處理成空串’’和mysql處理空串’’為0的問題

當數據庫字段類型是整數,如果參數變量為空字符串或者NULL,Mybatis會自動將參數賦值0,所以如果要判斷整數參數的多種狀態在傳遞數值到Mapper之前就要判斷是否為空字符串和NULL並將相應的狀態數值賦值給該參數,否則參數值等於空字符串、NULL和0得到的結果是一樣的。 

一般情況下,涉及到int類型的操作的時候,在Service中會統一把數字類型先變成字符串類型,然後再傳遞到Mapper中操作。

案例七:

使用mybatis 進行批量insert的時候 會自動封裝成一個map key是list 要存的數據變成了數組 需要注意在xml裡面如果使用自己定義的collection要在傳參時定義一個mapkey是自己定義的變量名哦。

在使用resultMap的時候,要把ID寫在第一行,否則的話,就會報錯。

優缺點

總結下mybatis的優缺點,以便大家對於mybatis的瞭解能更全面些。但我所說的優缺點,僅是我個人總結並結合使用體驗後得出的結果,並不能代表大眾想法,因此才以“淺談”作為文章標題。如果大家的見解與我不同,歡迎積極提出來一塊討論,我也藉以彌補自己認識的不足和短見。

優點:

  1. 易於上手和掌握。
  2. sql寫在xml裡,便於統一管理和優化。
  3. 解除sql與程序代碼的耦合。
  4. 提供映射標籤,支持對象與數據庫的orm字段關係映射
  5. 提供對象關係映射標籤,支持對象關係組建維護
  6. 提供xml標籤,支持編寫動態sql。

缺點:

  1. sql工作量很大,尤其是字段多、關聯表多時,更是如此。
  2. sql依賴於數據庫,導致數據庫移植性差。
  3. 由於xml裡標籤id必須唯一,導致DAO中方法不支持方法重載。
  4. 字段映射標籤和對象關係映射標籤僅僅是對映射關係的描述,具體實現仍然依賴於sql。(比如配置了一對多Collection標籤,如果sql裡沒有join子表或查詢子表的話,查詢後返回的對象是不具備對象關係的,即Collection的對象為null)
  5. DAO層過於簡單,對象組裝的工作量較大。
  6. 不支持級聯更新、級聯刪除。
  7. 編寫動態sql時,不方便調試,尤其邏輯複雜時。
  8. 8 提供的寫動態sql的xml標籤功能簡單(連struts都比不上),編寫動態sql仍然受限,且可讀性低。
  9. 若不查詢主鍵字段,容易造成查詢出的對象有“覆蓋”現象。
  10. 參數的數據類型支持不完善。(如參數為Date類型時,容易報沒有get、set方法,需在參數上加@param)
  11. 多參數時,使用不方便,功能不夠強大。(目前支持的方法有map、對象、註解@param以及默認採用012索引位的方式)
  12. 緩存使用不當,容易產生髒數據。

總結:

mybatis的優點其實也是mybatis的缺點,正因為mybatis使用簡單,數據的可靠性、完整性的瓶頸便更多依賴於程序員對sql的使用水平上了。sql寫在xml裡,雖然方便了修改、優化和統一瀏覽,但可讀性很低,調試也非常困難,也非常受限,無法像jdbc那樣在代碼里根據邏輯實現複雜動態sql拼接。mybatis簡單看就是提供了字段映射和對象關係映射的jdbc,省去了數據賦值到對象的步驟而已,除此以外並無太多作為,不要把它想象成hibernate那樣強大,簡單小巧易用上手,方便瀏覽修改sql就是它最大的優點了。

mybatis適用於小型且程序員能力較低的項目和人群使用,對於中大型項目來說我並不推薦使用,如果覺得hibernate效率低的話(實際上也是使用不當所致,hibernate是實際上是不適用於擁有高負載的工程項目),還不如直接用spring提供的jdbc簡單框架(Template),同樣支持對象映射。

來源:http://t.cn/EiVxHRU


搜索微信號(ID:芋道源碼),可以獲得各種 Java 源碼解析。

並且,回覆【書籍】後,可以領取筆者推薦的各種 Java 從入門到架構的書籍。

程序員,Mybatis 你踩過坑嗎?

來吧,騷年~


分享到:


相關文章: