大多數開發者應該都使用過Hibernate或者Mybatis的框架,或多或少都踩過一些坑!html
如在MyBatis/Ibatis中#和$的區別?#方式可以很大程度防止sql注入,$方式沒法防止Sql注入。因此,老司機 對新手說,最好用#。簡單的說#{}是通過預編譯的,是安全的,而java
解決一些實際問題。mysql
如在執行sql語句時你有時並不但願讓變量進行處理,而是直接賦值執行,這時就要用到(${a})了,在使用時還要這樣賦值 @Param(value="a") String a。sql
如日期問題:可能會遇到日期格式的時間段問題,當數據庫的時間爲DATE類型時,MyBatis的jdbcType應該使用DATE,jdbcType=DATE,而不是使用jdbcType=TIMESTAMP。數據庫
如在使用resultMap的時候,要把ID寫在第一行,不然的話,就會報錯。數組
又如最近在作的項目,遇到myBatis的大坑,Mybatis一直報異常Java.lang.ArrayIndexOutOfBoundsException,因而開始代碼查錯,代碼中有存儲過程,而後開發使用ROOT用戶執行SQL跑出來的數據結果集是正常的,在測試環境程序運行也正常,可是在正式環境就其餘用戶不行,最後發現是由於數據庫沒有給該用戶受權出了問題。緩存
# 案例一安全
做爲新手,在此記下剛踩的一個坑,「踩踩更健康= =踩過痛過纔不會再次錯」,寫了一個sql語句用到兩張表,兩張表中有兩個字段名字是同樣的都是Time和Content,而後要查詢這兩張表的這兩個字段都要查出來放到一個dto中,dto以下圖所示。bash
sql語句以下:網絡
然而運行後卻發現後幾個在數據庫表裏同名的字段取出來都是null。
可是放到數據庫那邊執行是沒有取出空數據的。
真是苦惱= =,後來經大神指點,sql語句查詢出來的這個字段名必須和dto的參數名一致,改爲這樣就經過了。
數據都取出來了。。。還記得在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…複製代碼
我想這個方法會比經過格式轉換的效率要高一些。
MyBatis中時間字段的使用–寫入。
寫入但是直接寫入Timestamp的數據,須要描述一些寫入的jdbcType,以下:{installTime, jdbcType=TIMESTAMP}。
二、Mapper層參數爲Map,由Service層負責重載。
Mapper因爲機制的問題,不能重載,參數通常設置成Map,但這樣會使參數變得模糊,若是想要使代碼變得清晰,能夠經過service層來實現重載的目的,對外提供的Service層是重載的,但這些重載的Service方法實際上是調同一個Mapper,只不過相應的參數並不一致。
也許有人會想,爲何不在Service層也設置成Map呢?我我的是不推薦這麼作的,雖然爲了方便,我在以前的項目中也大量採用了這種方式,但很明顯會給往後的維護工做帶來麻煩。由於這麼作會使你整個MVC都依賴於Map模型,這個模型實際上是很不錯的,方便搭框架,但存在一個問題:僅僅看方法簽名,你不清楚Map中所擁有的參數個數、類型、每一個參數表明的含義。
試想,你只對Service層變動,或者DAO層變動,你須要清楚整個流程中Map傳遞過來的參數,除非你註釋或者文檔良好,不然必須把每一層的代碼都瞭解清楚,你才知道傳遞了哪些參數。針對於簡單MVC,那倒也還好,但若是層次複雜以後,代碼會變得異常複雜,並且若是我增長一個參數,須要把每個層的註釋都添加上。相對於註釋,使用方法簽名來保證這種代碼可控性會來得更可行一些,由於註釋有多是過期的,但方法簽名通常不太多是陳舊的。
三、儘可能少用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_TEXTWHERE 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來完成動態分支的判斷,由於判斷分支過於複雜,並且難以維護。
四、用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 TESTWHERE COL1 > 1 -- 這裏是註釋AND COL2 = #{a}複製代碼
即便傳入的參數中存在對應的參數,實際也不會產生效果,由於後面的內容其實是被徹底註釋了。這種錯誤,若是不通過嚴格的測試,是很難發現的。通常狀況下,XML註釋徹底能夠替代SQL註釋,所以這種行爲應該能夠禁止掉。
五、儘量使用`#{}`,而不是`${}`。
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所致使的問題,最激進的方式是所有采用存儲過程,用數據庫原生的方式來解決,方便開發調試,固然也會帶來問題:對開發人員會有更高的要求、存儲過程的管理等等,我這邊項目沒有采用過這種方式,這裏不作更多的展開。
六、簡單使用Mybatis。
Mybatis的功能相對而言仍是比較弱的,缺乏了好多必要的輔助庫,字符串處理等等,擴展也比較困難,通常也就可能對返回值進行一些處理。所以最好僅僅把它做爲單純的SQL配置文件,以及簡單的ORM框架。不要嘗試在Mybatis中作過多的動態SQL,不然會致使後續的維護很是噁心。
# 案例七
使用mybatis 進行批量insert的時候,會自動封裝成一個map,key是list,要存的數據變成了數組,須要注意在xml裏面若是使用本身定義的collection要在傳參時定義一個mapkey是本身定義的變量名哦。
在使用resultMap的時候,要把ID寫在第一行,不然的話,就會報錯。
總結下mybatis的優缺點,以便你們對於mybatis的瞭解能更全面些。但我所說的優缺點,僅是我我的總結並結合使用體驗後得出的結果,並不能表明大衆想法,所以才以「淺談」做爲文章標題。若是你們的看法與我不一樣,歡迎積極提出來一塊討論,我也藉以彌補本身認識的不足和短見。
優勢:
易於上手和掌握。
sql寫在xml裏,便於統一管理和優化。
解除sql與程序代碼的耦合。
提供映射標籤,支持對象與數據庫的orm字段關係映射
提供對象關係映射標籤,支持對象關係組建維護
提供xml標籤,支持編寫動態sql。
缺點:
sql工做量很大,尤爲是字段多、關聯表多時,更是如此。
sql依賴於數據庫,致使數據庫移植性差。
因爲xml裏標籤id必須惟一,致使DAO中方法不支持方法重載。
字段映射標籤和對象關係映射標籤僅僅是對映射關係的描述,具體實現仍然依賴於sql。(好比配置了一對多Collection標籤,若是sql裏沒有join子表或查詢子表的話,查詢後返回的對象是不具有對象關係的,即Collection的對象爲null)
DAO層過於簡單,對象組裝的工做量較大。
不支持級聯更新、級聯刪除。
編寫動態sql時,不方便調試,尤爲邏輯複雜時。
8 提供的寫動態sql的xml標籤功能簡單(連struts都比不上),編寫動態sql仍然受限,且可讀性低。
若不查詢主鍵字段,容易形成查詢出的對象有「覆蓋」現象。
參數的數據類型支持不完善。(如參數爲Date類型時,容易報沒有get、set方法,需在參數上加@param)
多參數時,使用不方便,功能不夠強大。(目前支持的方法有map、對象、註解@param以及默認採用012索引位的方式)
緩存使用不當,容易產生髒數據。
# 幾點技巧總結
一、查詢不少字段時能夠提出來再引入到sql語句。
提取:
id, type, shopCouId, Path, fromDate, toDate, insDate, insUserId, updDate, updUserId, delFlg複製代碼
引入:
select<include refid="Base_Column_List" />from adinfowhere id = #{id,jdbcType=INTEGER}複製代碼
二、緩存使用
在增刪查改時,可使用緩存屬性控制數據緩存。
三、能夠判斷傳進來的參數,再進行操做
and langCd = #{langCd,jdbcType=VARCHAR}
四、能夠在sql語句中直接進行加減乘除計算,模糊查詢時,須要注意使用方式。
SELECT sum(b.netCnt) + #{netCnt,jdbcType=INTEGER}FROM collectcnt bWHERE 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中操做。
在我看來,雖然它有不少臭毛病,但它給我一種傻壯傻壯的感受,一切都由我來操做,因此我鍾情於它。