前面介紹了mybatis的insert侨赡,delete嗦嗡,update勋锤,select四個主要的參數(shù),也是映射文件中四個主要的標簽侥祭。除了這四個我們還使用了selectKey標簽生成了主鍵id叁执,下面我們來討論除了增刪改查這四個標簽之外的其它標簽。從這些當中也能看出mybatis的強大之處矮冬。
selectKey標簽?
先來回顧一下selectKey標簽谈宛,有時候新增一條數(shù)據,知道新增成功即可胎署。但是有時候吆录,需要這條新增數(shù)據的主鍵,以便邏輯使用硝拧,再將其查詢出來明顯不符合要求径筏,效率也變低了葛假。這時候,通過一些設置滋恬,mybatis可以將insert的數(shù)據的主鍵返回聊训,直接拿到新增數(shù)據的主鍵,以便后續(xù)使用恢氯。這里主要說的是selectKey標簽带斑。設計表的時候有兩種主鍵,一種自增主鍵勋拟,一般為int類型勋磕,一種為非自增的主鍵,例如用uuid等敢靡。
關于自增主鍵獲取id挂滓,我們直接指定insert標簽的useGeneratedKeys屬性和keyProperty屬性即可,并不用寫selectKey標簽:
下面說一下非自增主鍵啸胧,在代碼中生成的不用說赶站,代碼中直接就能獲取。非自增主鍵也可以在數(shù)據庫中直接生成纺念,比如oracle的序列贝椿,數(shù)據庫中的uuid,數(shù)據庫的隨機數(shù)等等陷谱。例如:
這種方式就得通過selectKey獲取烙博。這種方式需要參數(shù)類型里面有id這個屬性,在執(zhí)行insert之前會先吧獲取的id賦值到user里面的id屬性中烟逊,然后再正常執(zhí)行下面的insert語句渣窜。不過selectKey這種方式還是建議能不用最好不要用!
foreach標簽
很多時候我們傳入的參數(shù)可能不是一個數(shù)量確定的基礎類型或者對象類型焙格,而是一個集合或者數(shù)組類型图毕,里面包含了數(shù)量不確定的參數(shù)個數(shù),這時候可以在語句中使用foreach標簽處理參數(shù)眷唉。下面是foreach標簽的各個屬性:
collection:表示迭代集合的參數(shù)名稱
item:表示本次迭代獲取的元素名稱予颤,若collection為List、Set或者數(shù)組冬阳,則表示其中的元素蛤虐;若collection為map,則代表key-value的value肝陪,該參數(shù)為必選
open:表示該迭代以什么開始驳庭,最常用的是左括弧’(’,注意:mybatis會將該字符拼接到迭代sql語句之前,該參數(shù)為可選項
close:表示該語句以什么結束饲常,最常用的是右括弧’)’蹲堂,注意:mybatis會將該字符拼接到整體的sql語句之后,該參數(shù)為可選項
separator:mybatis會在每次迭代后智能判斷要不要給sql語句后面加上separator屬性指定的字符贝淤,該參數(shù)為可選項
index:在list柒竞、Set和數(shù)組中,index表示當前迭代的位置,在map中播聪,index代指是元素的key朽基,該參數(shù)是可選項。
前面有個例子是根據id查詢名稱离陶,下面看一個根據多個id查詢名稱的例子:
接口定義:
注意SQL語句中collection的命名稼虎,這種寫法的話,SQL語句中只能寫collection="collection"或者collection="list"招刨,如果想給集合參數(shù)專門起個別名霎俩,可以在接口中定義:
這樣SQL語句中就可以使用別名了:
再來看SQL語句,前面寫一個1=2是為了后面拼接SQL方便计济,可以想象如果傳入的參數(shù)是1和2兩個茸苇,那么最終SQL就是:
select name from t_user where 1=2 or id=1 or id=2
這種寫法雖然符合邏輯排苍,但是看上去讓SQL有點丑沦寂,后面介紹動態(tài)SQL的時候會看到更好的寫法。
上面介紹了一個批量查詢的例子淘衙,使用了foreach標簽的兩個參數(shù)传藏,下面介紹一個批量插入的例子:
這里要注意,雖然設置了每個foreach的內容用逗號間隔彤守,但是其實最后一條后面是不用寫逗號的毯侦,這時候mybatis會自己智能判斷,來看接口定義:
注意上面的批量插入語句只是適用于部分數(shù)據庫具垫,這里只是參考侈离,大家可以根據實際情況使用foreach。
還有一個問題也需要注意筝蚕,open和close屬性并不是設置到每行迭代語句的開頭和結尾卦碾,而是整體迭代的最前面和最后面,因此這里的批量插入的括號要在每行里自己寫起宽。
sql標簽
這個元素可以被用來定義可重用的 SQL 代碼段洲胖,這些 SQL 代碼可以被包含在其他語句中。它可以(在加載的時候)被靜態(tài)地設置參數(shù)坯沪。 在不同的包含語句中可以設置不同的值到參數(shù)占位符上绿映。比如:
這個 SQL 片段可以被包含在其他語句中,例如:
添加SQL片段要是用include標簽,其中refid屬性指向的就是sql標簽的id叉弦,也可以給SQL片段中的字段使用別名:
這樣語句也要隨之改變:
我們一整個映射文件當中丐一,同一個表每次使用的別名不一定是一樣了,可以在SQL片段中使用動態(tài)別名:
這樣在使用SQL片段的時候聲明一下即可:
include標簽中間可以添加屬性標簽淹冰,屬性標簽的name指向的是SQL片段里面的變量钝诚,value表示替換變量的值。大家可以想象榄棵,有了sql標簽凝颇,動態(tài)的屬性,表名和動態(tài)的條件都可以根據自己的需要設定疹鳄。不過為了簡單拧略,大部分sql標簽都建議用在數(shù)據庫字段上。
參數(shù)
之前討論的所有語句中瘪弓,使用的都是簡單參數(shù)垫蛆。實際上參數(shù)是 MyBatis 非常強大的元素。對于簡單的使用場景腺怯,大約 90% 的情況下你都不需要使用復雜的參數(shù)袱饭,比如:
id=#{id}
id是一個基礎類型的字段(int),是個簡單類型呛占,沒有內部屬性虑乖,所以直接傳遞值,如果是對象類型晾虑,例如:
就像上面疹味,如果 User 類型的參數(shù)對象傳遞到了語句中,id帜篇、name 屬性將會被查找糙捺,然后將它們的值傳入預處理語句的參數(shù)中。User是自定義的對象笙隙,我們還可以使用map:
入參類型相應的改變即可洪灯,SQL的寫法基本不變:
對于向語句中傳遞參數(shù)來說,這真是既簡單又有效竟痰。不過參數(shù)映射的功能遠不止于此签钩。首先,像 MyBatis 的其他部分一樣凯亮,參數(shù)也可以指定一個特殊的數(shù)據類型边臼。比如id參數(shù):
#{id,javaType=int,jdbcType=NUMERIC}
因此我們的SQL可以寫成:
像 MyBatis 的其它部分一樣,javaType 幾乎總是可以根據參數(shù)對象的類型確定下來假消,除非該對象是一個?HashMap柠并。這個時候,你需要顯式指定?javaType?來確保正確的類型處理器(TypeHandler)被使用。
提示: JDBC 要求臼予,如果一個列允許 null 值鸣戴,并且會傳遞值 null 的參數(shù),就必須要指定 JDBC Type粘拾。
對于數(shù)值類型窄锅,還有一個小數(shù)保留位數(shù)的設置,來指定小數(shù)點后保留的位數(shù)缰雇。例如:
#{money,javaType=double,jdbcType=NUMERIC,numericScale=2}
最后入偷,來看mode 屬性,我們在調用存儲過程的時候械哟,存儲過程有三種類型的參數(shù)疏之,分別為 IN(輸入參數(shù)),OUT(輸出參數(shù))暇咆,INOUT(輸入輸出參數(shù))锋爪。一個存儲過程,可以有多個 IN 參數(shù)爸业,至多有一個 OUT 或 INOUT 參數(shù)其骄。mode允許你指定?IN,OUT?或?INOUT?參數(shù)扯旷。如果參數(shù)的?mode?為?OUT?或?INOUT拯爽,就像你在指定輸出參數(shù)時所期望的行為那樣,參數(shù)對象的屬性實際值將會被改變薄霜。 如果?mode?為?OUT(或?INOUT)某抓,而且?jdbcType?為?CURSOR(也就是 Oracle 的 REFCURSOR),你必須指定一個?resultMap?引用來將結果集?ResultMap?映射到參數(shù)的類型上惰瓜。要注意這里的?javaType?屬性是可選的,如果留空并且 jdbcType 是?CURSOR汉矿,它會被自動地被設為?ResultMap(關于ResultMap后面會詳細介紹)崎坊。例如:
#{company, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
盡管mybatis的參數(shù)的所有這些選項很強大,但大多時候你只須簡單地指定屬性名洲拇,其他的事情 MyBatis 會自己去推斷奈揍,頂多要為可能為空的列指定?jdbcType。例如:
參數(shù)的字符串替換
默認情況下赋续,mybatis使用#{}接收參數(shù)男翰,使用?#{}?格式的語法會導致 MyBatis 創(chuàng)建?PreparedStatement?參數(shù)占位符并安全地設置參數(shù)(就像使用 ? 一樣)。 這樣做更安全纽乱,更迅速蛾绎,通常也是首選做法。不過有時你就是想直接在 SQL 語句中插入一個不轉義的字符串。 比如在很多情況下要插入一段SQL語句來作為參數(shù)租冠,比如一段order by鹏倘,或者在開發(fā)權限功能的時候一段代表動態(tài)權限的SQL語句,那這個時候顽爹,我們需要的不是自動處理參數(shù)纤泵,而是把參數(shù)像字符串一樣只做拼接即可。這時候可以使用:
?${}?
比如拼接一段order by:
select name?
from t_user?
where id=#{id}?
${orderBy}
這里 MyBatis 不會修改或轉義字符串镜粤。再舉一個例子捏题,比如查詢功能的條件不固定時,不一定會使用哪個字段肉渴,那也可以這樣寫:
select name
from t_user
where ${colName} = #{colVal}
不止字段名涉馅,甚至如果表名也不固定,也可以使用字符串替換的參數(shù)方法黄虱,這里不再舉例稚矿。不過有一點要注意,在正常的參數(shù)中如果也使用${}捻浦,要注意它是字符串拼接晤揣,也就是說如果是字符串,要這樣寫:
name = '${name}'
引號要自己加上朱灿。不過通常絕大部分的業(yè)務不建議使用${}昧识。
提示: 用這種方式接受用戶的輸入,并將其用于語句中的參數(shù)是不安全的盗扒,會導致潛在的 SQL 注入攻擊跪楞,因此要么不允許用戶輸入這些字段,要么自行轉義并檢驗侣灶。
結果映射
我們執(zhí)行select語句的時候甸祭,要在標簽中指定回參類型 resultType,表示使用哪個類來接收返回的參數(shù)褥影。除了resultType還有一種接收返回參數(shù)的屬性是resultMap池户,使用這個參數(shù)需要引用一個resultMap標簽所定義的內容,所以需要了解resultMap標簽凡怎。
resultMap?元素是 MyBatis 中最重要最強大的元素校焦。它可以讓你從 90% 的 JDBC?ResultSets?數(shù)據提取代碼中解放出來,并在一些情形下允許你進行一些 JDBC 不支持的操作统倒。實際上寨典,在為一些比如連接的復雜語句編寫映射代碼的時候,一份resultMap?能夠代替實現(xiàn)同等功能的長達數(shù)千行的代碼房匆。ResultMap 的設計思想是耸成,對于簡單的語句根本不需要配置resultMap?报亩,而對于復雜一點的語句只需要描述它們的關系就行了。簡單的語句前面介紹了很多墓猎,比如捆昏,對于簡單的沒有直接Java類接收的結果,我們直接只用hashmap接收:
上述語句只是簡單地將所有的列映射到?HashMap?的鍵上毙沾,這由?resultType?屬性指定骗卜。雖然在大部分情況下都夠用,但是 HashMap 不是一個很好的領域模型左胞。你的程序更可能會使用 JavaBean 或 POJO(Plain Old Java Objects寇仓,普通老式 Java 對象)作為領域模型。例如:
上面我們接收參數(shù)使用的是User類的別名user烤宙,前面已經討論過遍烦。使用別名時,MyBatis 會在幕后自動創(chuàng)建一個?ResultMap躺枕,再基于每個屬性名來映射列到 JavaBean 的屬性上服猪。如果列名和屬性名沒有精確匹配,可以在 SELECT 語句中對列使用別名(這是一個基本的 SQL 特性)來匹配標簽拐云。也就是說罢猪,我們給類定義別名,其實真正使用的是ResultMap叉瘩,但是這個過程是透明的膳帕。自動創(chuàng)建的resultMap就像下面這樣:
這面的種種用法,都是簡單的結果映射薇缅,都沒有明顯的自定義resultMap標簽危彩。
高級結果映射
MyBatis 創(chuàng)建時的一個思想是:數(shù)據庫不可能永遠是你所想或所需的那個樣子。 我們希望每個數(shù)據庫都具備良好的第三范式或 BCNF 范式泳桦,可惜它們不總都是這樣汤徽。 如果能有一種完美的數(shù)據庫映射模式,所有應用程序都可以使用它蓬痒,那就太好了泻骤,但可惜也沒有。 而 ResultMap 就是 MyBatis 對這個問題的答案梧奢。
比如對于一個非常非常復雜的查詢,關聯(lián)了五六張表演痒,查詢的結果字段是好幾張表的部分字段整合在一起的結果亲轨,這時候肯定沒有一個和表對應的實體類能夠接收這個結果。我們如果使用resultType鸟顺,那么有兩種解決方法惦蚊,一個是直接使用map接收器虾,雖然寫上去簡單了,但是對于業(yè)務的清晰和維護無疑是成本很大的蹦锋。而且map并不是一個好的模型兆沙。另一個辦法是專門為了這個SQL創(chuàng)建一個實體類,用來接收這個結果莉掂,在很多項目中都能看到這種做法葛圃。
但是最好的方法是自定義一個resultMap,自定義一個結果映射憎妙,要比寫很多代碼库正,或者直接使用map好的多。比如我們來看下面這個SQL語句:
算是一個比較復雜的了厘唾,你可能想把它映射到一個智能的對象模型,這個對象表示了一篇博客,它由某位作者所寫背零,有很多的博文荆隘,每篇博文有零或多條的評論和標簽。 我們來看看下面這個完整的resulteMap例子鹤树,它是一個非常復雜的結果映射(假設作者铣焊,博客,博文魂迄,評論和標簽都是類型別名)粗截。 下面我們會一步一步來說明。雖然它看起來復雜捣炬,但其實非常簡單熊昌。
可以看到一個resultMap標簽,里面還包含了各種子標簽湿酸。resultMap?元素有很多子元素和一個值得深入探討的結構婿屹。 下面是resultMap?元素的概念視圖。
錯落有致的屬性說明推溃,也表示了標簽的使用位置和用法昂利。注意這些都是resultMap得子元素,并不是標簽內部的屬性铁坎,關于屬性的說明如下:
其中id好理解蜂奸,就是一個唯一標識,引用的時候指向的就是id硬萍,type指向的是整個結果映射到的具體類扩所,與resultType的一樣。
最佳實踐: 最好一步步地建立結果映射朴乖。單元測試可以在這個過程中起到很大幫助祖屏。 如果你嘗試一次創(chuàng)建一個像上面示例那樣的巨大的結果映射助赞,那么很可能會出現(xiàn)錯誤而且很難去使用它來完成工作。 從最簡單的形態(tài)開始袁勺,逐步迭代雹食。而且別忘了單元測試! 使用框架的缺點是有時候它們看上去像黑盒子(無論源代碼是否可見)期丰。 為了確保你實現(xiàn)的行為和想要的一致群叶,最好的選擇是編寫單元測試。提交 bug 的時候它也能起到很大的作用咐汞。
下面一步一步細說每一部分盖呼。先來看id和result:
這些是結果映射最基本的內容。id?和?result?元素都將一個列的值映射到一個簡單數(shù)據類型(String, int, double, Date 等)的屬性或字段化撕。這兩者之間的唯一不同是几晤,id?元素表示的結果將是對象的標識屬性,例如主鍵植阴,這會在比較對象實例時用到蟹瘾。 這樣可以提高整體的性能,尤其是進行緩存和嵌套結果映射(也就是連接映射)的時候掠手。下面看一下兩個元素都有的一些屬性:
為了以后可能的使用場景憾朴,MyBatis 通過內置的 jdbcType 枚舉類型支持下面的 JDBC 類型。
通過id和result元素可以滿足大多數(shù)的數(shù)據傳輸對象(Data Transfer Object, DTO)以及絕大部分領域模型的要求喷鸽。但有些情況下你想使用不可變類众雷。 一般來說,很少改變或基本不變的包含引用或數(shù)據的表做祝,很適合使用不可變類砾省。 構造方法注入允許你在初始化時為類設置屬性的值,而不用暴露出公有方法混槐。MyBatis 也支持私有屬性和私有 JavaBean 屬性來完成注入编兄,但有一些人更青睞于通過構造方法進行注入。?constructor?元素就是為此而生的声登。來看一個簡單的構造方法:
public User(Integer id, String username, int age) { }
為了將結果注入構造方法狠鸳,MyBatis 需要通過某種方式定位相應的構造方法。 在下面的例子中悯嗓,MyBatis 搜索一個聲明了三個形參的的構造方法件舵,參數(shù)類型以?java.lang.Integer,?java.lang.String?和?int?的順序給出。
當你在處理一個帶有多個形參的構造方法時脯厨,很容易搞亂 arg 元素的順序芦圾。 從版本 3.4.3 開始,可以在指定參數(shù)名稱的前提下俄认,以任意順序編寫 arg 元素个少。 為了通過名稱來引用構造方法參數(shù),你可以添加?@Param?注解眯杏,或者使用 '-parameters' 編譯選項并啟用?useActualParamName?選項(默認開啟)來編譯項目夜焦。下面是一個等價的例子,盡管函數(shù)簽名中第二和第三個形參的順序與 constructor 元素中參數(shù)聲明的順序不匹配岂贩。
如果名稱和類型的屬性相同茫经,那么可以省略?javaType?。其它元素標簽的屬性和id以及result的是差不多的:
上面直接配置屬性和配置構造器其實就是創(chuàng)建對象實例的兩種不同方法而已萎津,Java中基本也都是使用set方法賦值或者創(chuàng)建對象時直接使用有參數(shù)的構造器創(chuàng)建卸伞。原理上都差不多。
多對一關聯(lián)
再來看一下關聯(lián)锉屈,數(shù)據庫中的關聯(lián)關系大致上分為一對多荤傲、多對一和多對多三種情況。下面看一個多對一的例子:
上面的關聯(lián)使用了association標簽颈渊,關聯(lián)(association)元素處理“有一個”類型的關系遂黍。說簡單些就是在多對一的關系中,查詢多方的其中一個俊嗽,順便把所對應的一方查出來雾家。 比如,在我們的示例中绍豁,一個博客對應一個用戶(作者)芯咧。關聯(lián)結果映射和其它類型的映射工作方式差不多。 你需要指定目標屬性名以及屬性的javaType(很多時候 MyBatis 可以自己推斷出來竹揍,所以不用寫)敬飒,在必要的情況下你還可以設置 JDBC 類型,如果你想覆蓋獲取結果值的過程鬼佣,還可以設置類型處理器驶拱。
關聯(lián)的不同之處是,你需要告訴 MyBatis 如何加載關聯(lián)晶衷。MyBatis 有兩種不同的方式加載關聯(lián):
嵌套 Select 查詢:通過執(zhí)行另外一個 SQL 映射語句來加載期望的復雜類型蓝纲。
嵌套結果映射:使用嵌套的結果映射來處理連接結果的重復子集。
首先晌纫,先讓我們來看看這個元素的屬性税迷。你將會發(fā)現(xiàn),和普通的結果映射相比锹漱,它只在 select 和 resultMap 屬性上有所不同箭养。
先來看一個關聯(lián)的嵌套 Select 查詢的例子,先來看用到的屬性:
再看具體映射文件中SQL的寫法:
就是這么簡單哥牍。我們有兩個 select 查詢語句:一個用來加載博客(Blog)毕泌,另外一個用來加載作者(Author)喝检,而且博客的結果映射描述了應該使用?selectAuthor?語句加載它的 author 屬性。其它所有的屬性將會被自動加載撼泛,只要它們的列名和屬性名相匹配挠说。
這種方式雖然很簡單,但在大型數(shù)據集或大型數(shù)據表上表現(xiàn)不佳愿题。這個問題被稱為“N+1 查詢問題”损俭。 概括地講,N+1 查詢問題是這樣子的:
你執(zhí)行了一個單獨的 SQL 語句來獲取結果的一個列表(就是“+1”)潘酗。
對列表返回的每條記錄杆兵,你執(zhí)行一個 select 查詢語句來為每條記錄加載詳細信息(就是“N”)。
這個問題會導致成百上千的 SQL 語句被執(zhí)行仔夺。有時候琐脏,我們不希望產生這樣的后果。好消息是囚灼,MyBatis 能夠對這樣的查詢進行延遲加載骆膝,因此可以將大量語句同時運行的開銷分散開來。 然而灶体,如果你加載記錄列表之后立刻就遍歷列表以獲取嵌套的數(shù)據阅签,就會觸發(fā)所有的延遲加載查詢,性能可能會變得很糟糕蝎抽。所以還有另外一種方法政钟。就是關聯(lián)的嵌套結果映射。
我們來看關聯(lián)的嵌套結果映射的例子樟结,先來看用到的屬性:
之前养交,你已經看到了一個非常復雜的嵌套關聯(lián)的例子。 下面的例子則是一個非常簡單的例子瓢宦,用于演示嵌套結果映射如何工作碎连。 現(xiàn)在我們將博客表和作者表連接在一起,而不是執(zhí)行一個獨立的查詢語句驮履,就像這樣:
注意查詢中的連接鱼辙,以及為確保結果能夠擁有唯一且清晰的名字,我們設置的別名玫镐。 這使得進行映射非常簡單〉瓜罚現(xiàn)在我們可以映射這個結果:
在上面的例子中,你可以看到恐似,博客(Blog)作者(author)的關聯(lián)元素委托名為 “authorResult” 的結果映射來加載作者對象的實例杜跷。
非常重要: id 元素在嵌套結果映射中扮演著非常重要的角色。你應該總是指定一個或多個可以唯一標識結果的屬性。 雖然葛闷,即使不指定這個屬性憋槐,MyBatis 仍然可以工作,但是會產生嚴重的性能問題孵运。 只需要指定可以唯一標識結果的最少屬性秦陋。顯然,你可以選擇主鍵(復合主鍵也可以)治笨。
現(xiàn)在,上面的示例使用了外部的結果映射元素來映射關聯(lián)赤嚼。這使得 Author 的結果映射可以被重用旷赖。 然而,如果你不打算重用它更卒,或者你更喜歡將你所有的結果映射放在一個具有描述性的結果映射元素中等孵。 你可以直接將結果映射作為子元素嵌套在內。這里給出使用這種方式的等效例子:
那如果博客(blog)有一個共同作者(co-author)該怎么辦蹂空?select 語句看起來會是這樣的:
回憶一下俯萌,Author 的結果映射定義如下:
由于結果中的列名與結果映射中的列名不同。你需要指定?columnPrefix?以便重復使用該結果映射來映射 co-author 的結果上枕。
下面來看一下關聯(lián)的多結果集(ResultSet)的情況咐熙,先看屬性:
從版本 3.2.3 開始,MyBatis 提供了另一種解決 N+1 查詢問題的方法辨萍。某些數(shù)據庫允許存儲過程返回多個結果集棋恼,或一次性執(zhí)行多個語句,每個語句返回一個結果集锈玉。 我們可以利用這個特性爪飘,在不使用連接的情況下,只訪問數(shù)據庫一次就能獲得相關數(shù)據拉背。在例子中师崎,存儲過程執(zhí)行下面的查詢并返回兩個結果集。第一個結果集會返回博客(Blog)的結果椅棺,第二個則返回作者(Author)的結果犁罩。
在映射語句中,必須通過?resultSets?屬性為每個結果集指定一個名字土陪,多個名字使用逗號隔開昼汗。
現(xiàn)在我們可以指定使用 “authors” 結果集的數(shù)據來填充 “author” 關聯(lián):
一對多關聯(lián)
我們已經在上面看到了如何處理多對一類型的關聯(lián)。但是該怎么處理一對多類型的關聯(lián)呢鬼雀?這就是我們接下來要介紹的顷窒。一對多的關聯(lián)使用集合(collection )標簽,例如:
集合元素和關聯(lián)元素幾乎是一樣的,它們相似的程度之高鞋吉,以致于沒有必要再介紹集合元素的相似部分鸦做。 所以讓我們來關注它們的不同之處吧。我們來繼續(xù)上面的示例谓着,一個博客(Blog)只有一個作者(Author)泼诱。但一個博客有很多文章(Post)。 在博客類中赊锚,這可以用下面的寫法來表示治筒,也就是一方包含多方的通俗做法:
private List<Post> posts;
要像上面這樣,映射嵌套結果集合到一個 List 中舷蒲,可以使用集合元素耸袜。 和關聯(lián)元素一樣,我們可以使用嵌套 Select 查詢牲平,或基于連接的嵌套結果映射集合堤框。
首先來看集合的嵌套 Select 查詢,讓我們看看如何使用嵌套 Select 查詢來為博客加載文章纵柿。
你可能會立刻注意到幾個不同蜈抓,但大部分都和我們上面學習過的關聯(lián)元素非常相似。 首先昂儒,你會注意到我們使用的是集合元素沟使。 接下來你會注意到有一個新的 “ofType” 屬性。這個屬性非常重要荆忍,它用來將 JavaBean(或字段)屬性的類型和集合存儲的類型區(qū)分開來格带。?在一般情況下,MyBatis 可以推斷 javaType 屬性刹枉,因此并不需要填寫叽唱。所以很多時候你可以簡略成:
下面來看集合的嵌套結果映射,現(xiàn)在你可能已經猜到了集合的嵌套結果映射是怎樣工作的——除了新增的 “ofType” 屬性微宝,它和關聯(lián)的完全相同棺亭。首先, 讓我們看看對應的 SQL 語句:
我們再次連接了博客表和文章表,并且為每一列都賦予了一個有意義的別名蟋软,以便映射保持簡單镶摘。 要映射博客里面的文章集合,就這么簡單:
再提醒一次岳守,要記得上面 id 元素的重要性凄敢,如果你不記得了,請閱讀關聯(lián)部分的相關部分湿痢。如果你喜歡更詳略的涝缝、可重用的結果映射扑庞,你可以使用下面的等價形式:
再看集合的多結果集(ResultSet),像關聯(lián)元素那樣拒逮,我們可以通過執(zhí)行存儲過程實現(xiàn)罐氨,它會執(zhí)行兩個查詢并返回兩個結果集,一個是博客的結果集滩援,另一個是文章的結果集:
在映射語句中栅隐,必須通過?resultSets?屬性為每個結果集指定一個名字,多個名字使用逗號隔開玩徊。
我們指定 “posts” 集合將會使用存儲在 “posts” 結果集中的數(shù)據進行填充:
注意: 對關聯(lián)或集合的映射租悄,并沒有深度、廣度或組合上的要求佣赖。但在映射時要留意性能問題恰矩。 在探索最佳實踐的過程中,應用的單元測試和性能測試會是你的好幫手憎蛤。 而 MyBatis 的好處在于,可以在不對你的代碼引入重大變更(如果有)的情況下纪吮,允許你之后改變你的想法俩檬。
高級關聯(lián)和集合映射是一個深度話題。就介紹到這里碾盟,配合少許的實踐棚辽,你會很快了解全部的用法。
鑒別器
下面介紹一下鑒別器冰肴,先來看標簽:
有時候屈藐,一個數(shù)據庫查詢可能會返回多個不同的結果集(但總體上還是有一定的聯(lián)系的)。 鑒別器(discriminator)元素就是被設計來應對這種情況的熙尉,另外也能處理其它情況联逻,例如類的繼承層次結構。 鑒別器的概念很好理解——它很像 Java 語言中的 switch 語句检痰。
一個鑒別器的定義需要指定 column 和 javaType 屬性包归。column 指定了 MyBatis 查詢被比較值的地方。 而 javaType 用來確保使用正確的相等測試(雖然很多情況下字符串的相等測試都可以工作)铅歼。例如:
在這個示例中公壤,MyBatis 會從結果集中得到每條記錄,然后比較它的 vehicle type 值椎椰。 如果它匹配任意一個鑒別器的 case厦幅,就會使用這個 case 指定的結果映射。 這個過程是互斥的慨飘,也就是說确憨,剩余的結果映射將被忽略(除非它是擴展的,我們將在稍后討論它)。 如果不能匹配任何一個 case缚态,MyBatis 就只會使用鑒別器塊外定義的結果映射磁椒。 所以,如果 carResult 的聲明如下:
那么只有 doorCount 屬性會被加載玫芦。這是為了即使鑒別器的 case 之間都能分為完全獨立的一組浆熔,盡管和父結果映射可能沒有什么關系。在上面的例子中桥帆,我們當然知道 cars 和 vehicles 之間有關系医增,也就是 Car 是一個 Vehicle。因此老虫,我們希望剩余的屬性也能被加載叶骨。而這只需要一個小修改。
現(xiàn)在 vehicleResult 和 carResult 的屬性都會被加載了祈匙『龉簦可能有人又會覺得映射的外部定義有點太冗長了。 因此夺欲,對于那些更喜歡簡潔的映射風格的人來說跪帝,還有另一種語法可以選擇。例如:
提示: 請注意些阅,這些都是結果映射伞剑,如果你完全不設置任何的 result 元素,MyBatis 將為你自動匹配列和屬性市埋。所以上面的例子大多都要比實際的更復雜黎泣。 這也表明,大多數(shù)數(shù)據庫的復雜度都比較高缤谎,我們不太可能一直依賴于這種機制抒倚。
自動映射
正如上面所說,在簡單的場景下弓千,MyBatis 可以為你自動映射查詢結果衡便。但如果遇到復雜的場景,你需要構建一個結果映射洋访。 但是在下面镣陕,你將看到,你可以混合使用這兩種策略姻政。讓我們深入了解一下自動映射是怎樣工作的呆抑。
當自動映射查詢結果時,MyBatis 會獲取結果中返回的列名并在 Java 類中查找相同名字的屬性(忽略大小寫)汁展。 這意味著如果發(fā)現(xiàn)了?ID?列和?id?屬性鹊碍,MyBatis 會將列?ID?的值賦給?id?屬性厌殉。
通常數(shù)據庫列使用大寫字母組成的單詞命名,單詞間用下劃線分隔侈咕;而 Java 屬性一般遵循駝峰命名法約定公罕。為了在這兩種命名方式之間啟用自動映射,需要將?mapUnderscoreToCamelCase?設置為 true耀销。設置位置在mybatis的全局配置文件中楼眷,需要在settings標簽下面設置,具體設置如下:
甚至在提供了結果映射后熊尉,自動映射也能工作罐柳。在這種情況下,對于每一個結果映射狰住,在 ResultSet 出現(xiàn)的列张吉,如果沒有設置手動映射,將被自動映射催植。在自動映射處理完畢后肮蛹,再處理手動映射。 在下面的例子中创南,id?和?userName?列將被自動映射蔗崎,hashed_password?列將根據配置進行映射。
有三種自動映射等級:
NONE?- 禁用自動映射扰藕。僅對手動映射的屬性進行映射。
PARTIAL?- 對除在內部定義了嵌套結果映射(也就是連接的屬性)以外的屬性進行映射
FULL?- 自動映射所有屬性芳撒。
默認值是?PARTIAL邓深,這是有原因的。當對連接查詢的結果使用?FULL?時笔刹,連接查詢會在同一行中獲取多個不同實體的數(shù)據芥备,因此可能導致非預期的映射。 下面的例子將展示這種風險:
在該結果映射中舌菜,Blog?和?Author?均將被自動映射萌壳。但是注意?Author?有一個?id?屬性,在 ResultSet 中也有一個名為?id?的列日月,所以 Author 的 id 將填入 Blog 的 id袱瓮,這可不是你期望的行為。 所以爱咬,要謹慎使用?FULL尺借。
無論設置的自動映射等級是哪種,你都可以通過在結果映射上設置?autoMapping?屬性來為指定的結果映射設置啟用/禁用自動映射精拟。
多參數(shù)傳參
現(xiàn)在傳參時都是在標簽內設置參數(shù)的類型的燎斩,這是因為我們在接口定義的時候虱歪,大部分情況只傳入了一個參數(shù):
Integer? insert(User user);
如果傳入的參數(shù)是多個怎么處理呢?比如:
我們可以把多個參數(shù)合并成一個栅表,比如放到map里面笋鄙,或者專門新建一個實體類存儲這些參數(shù),但是mybatis還提供了一種更簡單的方法怪瓶,就是在接口這里聲明每個參數(shù):
我們在形參的定義類型前面加了一個注解萧落,表示聲明參數(shù),注解中傳入了一個字符串劳殖,表示參數(shù)的名字铐尚,這個名字可以在SQL語句中使用。下面看一下SQL語句哆姻,我們既然在接口這里聲明了參數(shù)宣增,那么在SQL標簽里,就不用了再寫parameterType了:
通過這種方式可以傳遞多個參數(shù)矛缨,前面講的foreach標簽里面給集合定義別名也使用了這個注解爹脾,大家可以對比看下,充分了解這個注解的作用箕昭。