前面介紹了mybatis的insert,delete,update,select四個主要的參數,也是映射文件中四個主要的標簽。除了這四個我們還使用了selectKey標簽生成了主鍵id,下面我們來討論除了增刪改查這四個標簽之外的其它標簽。從這些當中也能看出mybatis的強大之處。
selectKey標簽?
先來回顧一下selectKey標簽,有時候新增一條數據,知道新增成功即可。但是有時候,需要這條新增數據的主鍵,以便邏輯使用,再將其查詢出來明顯不符合要求,效率也變低了。這時候,通過一些設置,mybatis可以將insert的數據的主鍵返回,直接拿到新增數據的主鍵,以便后續使用。這里主要說的是selectKey標簽。設計表的時候有兩種主鍵,一種自增主鍵,一般為int類型,一種為非自增的主鍵,例如用uuid等。
關于自增主鍵獲取id,我們直接指定insert標簽的useGeneratedKeys屬性和keyProperty屬性即可,并不用寫selectKey標簽:
下面說一下非自增主鍵,在代碼中生成的不用說,代碼中直接就能獲取。非自增主鍵也可以在數據庫中直接生成,比如oracle的序列,數據庫中的uuid,數據庫的隨機數等等。例如:
這種方式就得通過selectKey獲取。這種方式需要參數類型里面有id這個屬性,在執行insert之前會先吧獲取的id賦值到user里面的id屬性中,然后再正常執行下面的insert語句。不過selectKey這種方式還是建議能不用最好不要用!
foreach標簽
很多時候我們傳入的參數可能不是一個數量確定的基礎類型或者對象類型,而是一個集合或者數組類型,里面包含了數量不確定的參數個數,這時候可以在語句中使用foreach標簽處理參數。下面是foreach標簽的各個屬性:
collection:表示迭代集合的參數名稱
item:表示本次迭代獲取的元素名稱,若collection為List、Set或者數組,則表示其中的元素;若collection為map,則代表key-value的value,該參數為必選
open:表示該迭代以什么開始,最常用的是左括弧’(’,注意:mybatis會將該字符拼接到迭代sql語句之前,該參數為可選項
close:表示該語句以什么結束,最常用的是右括弧’)’,注意:mybatis會將該字符拼接到整體的sql語句之后,該參數為可選項
separator:mybatis會在每次迭代后智能判斷要不要給sql語句后面加上separator屬性指定的字符,該參數為可選項
index:在list、Set和數組中,index表示當前迭代的位置,在map中,index代指是元素的key,該參數是可選項。
前面有個例子是根據id查詢名稱,下面看一個根據多個id查詢名稱的例子:
接口定義:
注意SQL語句中collection的命名,這種寫法的話,SQL語句中只能寫collection="collection"或者collection="list",如果想給集合參數專門起個別名,可以在接口中定義:
這樣SQL語句中就可以使用別名了:
再來看SQL語句,前面寫一個1=2是為了后面拼接SQL方便,可以想象如果傳入的參數是1和2兩個,那么最終SQL就是:
select name from t_user where 1=2 or id=1 or id=2
這種寫法雖然符合邏輯,但是看上去讓SQL有點丑,后面介紹動態SQL的時候會看到更好的寫法。
上面介紹了一個批量查詢的例子,使用了foreach標簽的兩個參數,下面介紹一個批量插入的例子:
這里要注意,雖然設置了每個foreach的內容用逗號間隔,但是其實最后一條后面是不用寫逗號的,這時候mybatis會自己智能判斷,來看接口定義:
注意上面的批量插入語句只是適用于部分數據庫,這里只是參考,大家可以根據實際情況使用foreach。
還有一個問題也需要注意,open和close屬性并不是設置到每行迭代語句的開頭和結尾,而是整體迭代的最前面和最后面,因此這里的批量插入的括號要在每行里自己寫。
sql標簽
這個元素可以被用來定義可重用的 SQL 代碼段,這些 SQL 代碼可以被包含在其他語句中。它可以(在加載的時候)被靜態地設置參數。 在不同的包含語句中可以設置不同的值到參數占位符上。比如:
這個 SQL 片段可以被包含在其他語句中,例如:
添加SQL片段要是用include標簽,其中refid屬性指向的就是sql標簽的id,也可以給SQL片段中的字段使用別名:
這樣語句也要隨之改變:
我們一整個映射文件當中,同一個表每次使用的別名不一定是一樣了,可以在SQL片段中使用動態別名:
這樣在使用SQL片段的時候聲明一下即可:
include標簽中間可以添加屬性標簽,屬性標簽的name指向的是SQL片段里面的變量,value表示替換變量的值。大家可以想象,有了sql標簽,動態的屬性,表名和動態的條件都可以根據自己的需要設定。不過為了簡單,大部分sql標簽都建議用在數據庫字段上。
參數
之前討論的所有語句中,使用的都是簡單參數。實際上參數是 MyBatis 非常強大的元素。對于簡單的使用場景,大約 90% 的情況下你都不需要使用復雜的參數,比如:
id=#{id}
id是一個基礎類型的字段(int),是個簡單類型,沒有內部屬性,所以直接傳遞值,如果是對象類型,例如:
就像上面,如果 User 類型的參數對象傳遞到了語句中,id、name 屬性將會被查找,然后將它們的值傳入預處理語句的參數中。User是自定義的對象,我們還可以使用map:
入參類型相應的改變即可,SQL的寫法基本不變:
對于向語句中傳遞參數來說,這真是既簡單又有效。不過參數映射的功能遠不止于此。首先,像 MyBatis 的其他部分一樣,參數也可以指定一個特殊的數據類型。比如id參數:
#{id,javaType=int,jdbcType=NUMERIC}
因此我們的SQL可以寫成:
像 MyBatis 的其它部分一樣,javaType 幾乎總是可以根據參數對象的類型確定下來,除非該對象是一個?HashMap。這個時候,你需要顯式指定?javaType?來確保正確的類型處理器(TypeHandler)被使用。
提示: JDBC 要求,如果一個列允許 null 值,并且會傳遞值 null 的參數,就必須要指定 JDBC Type。
對于數值類型,還有一個小數保留位數的設置,來指定小數點后保留的位數。例如:
#{money,javaType=double,jdbcType=NUMERIC,numericScale=2}
最后,來看mode 屬性,我們在調用存儲過程的時候,存儲過程有三種類型的參數,分別為 IN(輸入參數),OUT(輸出參數),INOUT(輸入輸出參數)。一個存儲過程,可以有多個 IN 參數,至多有一個 OUT 或 INOUT 參數。mode允許你指定?IN,OUT?或?INOUT?參數。如果參數的?mode?為?OUT?或?INOUT,就像你在指定輸出參數時所期望的行為那樣,參數對象的屬性實際值將會被改變。 如果?mode?為?OUT(或?INOUT),而且?jdbcType?為?CURSOR(也就是 Oracle 的 REFCURSOR),你必須指定一個?resultMap?引用來將結果集?ResultMap?映射到參數的類型上。要注意這里的?javaType?屬性是可選的,如果留空并且 jdbcType 是?CURSOR,它會被自動地被設為?ResultMap(關于ResultMap后面會詳細介紹)。例如:
#{company, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
盡管mybatis的參數的所有這些選項很強大,但大多時候你只須簡單地指定屬性名,其他的事情 MyBatis 會自己去推斷,頂多要為可能為空的列指定?jdbcType。例如:
參數的字符串替換
默認情況下,mybatis使用#{}接收參數,使用?#{}?格式的語法會導致 MyBatis 創建?PreparedStatement?參數占位符并安全地設置參數(就像使用 ? 一樣)。 這樣做更安全,更迅速,通常也是首選做法。不過有時你就是想直接在 SQL 語句中插入一個不轉義的字符串。 比如在很多情況下要插入一段SQL語句來作為參數,比如一段order by,或者在開發權限功能的時候一段代表動態權限的SQL語句,那這個時候,我們需要的不是自動處理參數,而是把參數像字符串一樣只做拼接即可。這時候可以使用:
?${}?
比如拼接一段order by:
select name?
from t_user?
where id=#{id}?
${orderBy}
這里 MyBatis 不會修改或轉義字符串。再舉一個例子,比如查詢功能的條件不固定時,不一定會使用哪個字段,那也可以這樣寫:
select name
from t_user
where ${colName} = #{colVal}
不止字段名,甚至如果表名也不固定,也可以使用字符串替換的參數方法,這里不再舉例。不過有一點要注意,在正常的參數中如果也使用${},要注意它是字符串拼接,也就是說如果是字符串,要這樣寫:
name = '${name}'
引號要自己加上。不過通常絕大部分的業務不建議使用${}。
提示: 用這種方式接受用戶的輸入,并將其用于語句中的參數是不安全的,會導致潛在的 SQL 注入攻擊,因此要么不允許用戶輸入這些字段,要么自行轉義并檢驗。
結果映射
我們執行select語句的時候,要在標簽中指定回參類型 resultType,表示使用哪個類來接收返回的參數。除了resultType還有一種接收返回參數的屬性是resultMap,使用這個參數需要引用一個resultMap標簽所定義的內容,所以需要了解resultMap標簽。
resultMap?元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90% 的 JDBC?ResultSets?數據提取代碼中解放出來,并在一些情形下允許你進行一些 JDBC 不支持的操作。實際上,在為一些比如連接的復雜語句編寫映射代碼的時候,一份resultMap?能夠代替實現同等功能的長達數千行的代碼。ResultMap 的設計思想是,對于簡單的語句根本不需要配置resultMap?,而對于復雜一點的語句只需要描述它們的關系就行了。簡單的語句前面介紹了很多,比如,對于簡單的沒有直接Java類接收的結果,我們直接只用hashmap接收:
上述語句只是簡單地將所有的列映射到?HashMap?的鍵上,這由?resultType?屬性指定。雖然在大部分情況下都夠用,但是 HashMap 不是一個很好的領域模型。你的程序更可能會使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 對象)作為領域模型。例如:
上面我們接收參數使用的是User類的別名user,前面已經討論過。使用別名時,MyBatis 會在幕后自動創建一個?ResultMap,再基于每個屬性名來映射列到 JavaBean 的屬性上。如果列名和屬性名沒有精確匹配,可以在 SELECT 語句中對列使用別名(這是一個基本的 SQL 特性)來匹配標簽。也就是說,我們給類定義別名,其實真正使用的是ResultMap,但是這個過程是透明的。自動創建的resultMap就像下面這樣:
這面的種種用法,都是簡單的結果映射,都沒有明顯的自定義resultMap標簽。
高級結果映射
MyBatis 創建時的一個思想是:數據庫不可能永遠是你所想或所需的那個樣子。 我們希望每個數據庫都具備良好的第三范式或 BCNF 范式,可惜它們不總都是這樣。 如果能有一種完美的數據庫映射模式,所有應用程序都可以使用它,那就太好了,但可惜也沒有。 而 ResultMap 就是 MyBatis 對這個問題的答案。
比如對于一個非常非常復雜的查詢,關聯了五六張表,查詢的結果字段是好幾張表的部分字段整合在一起的結果,這時候肯定沒有一個和表對應的實體類能夠接收這個結果。我們如果使用resultType,那么有兩種解決方法,一個是直接使用map接收,雖然寫上去簡單了,但是對于業務的清晰和維護無疑是成本很大的。而且map并不是一個好的模型。另一個辦法是專門為了這個SQL創建一個實體類,用來接收這個結果,在很多項目中都能看到這種做法。
但是最好的方法是自定義一個resultMap,自定義一個結果映射,要比寫很多代碼,或者直接使用map好的多。比如我們來看下面這個SQL語句:
算是一個比較復雜的了,你可能想把它映射到一個智能的對象模型,這個對象表示了一篇博客,它由某位作者所寫,有很多的博文,每篇博文有零或多條的評論和標簽。 我們來看看下面這個完整的resulteMap例子,它是一個非常復雜的結果映射(假設作者,博客,博文,評論和標簽都是類型別名)。 下面我們會一步一步來說明。雖然它看起來復雜,但其實非常簡單。
可以看到一個resultMap標簽,里面還包含了各種子標簽。resultMap?元素有很多子元素和一個值得深入探討的結構。 下面是resultMap?元素的概念視圖。
錯落有致的屬性說明,也表示了標簽的使用位置和用法。注意這些都是resultMap得子元素,并不是標簽內部的屬性,關于屬性的說明如下:
其中id好理解,就是一個唯一標識,引用的時候指向的就是id,type指向的是整個結果映射到的具體類,與resultType的一樣。
最佳實踐: 最好一步步地建立結果映射。單元測試可以在這個過程中起到很大幫助。 如果你嘗試一次創建一個像上面示例那樣的巨大的結果映射,那么很可能會出現錯誤而且很難去使用它來完成工作。 從最簡單的形態開始,逐步迭代。而且別忘了單元測試! 使用框架的缺點是有時候它們看上去像黑盒子(無論源代碼是否可見)。 為了確保你實現的行為和想要的一致,最好的選擇是編寫單元測試。提交 bug 的時候它也能起到很大的作用。
下面一步一步細說每一部分。先來看id和result:
這些是結果映射最基本的內容。id?和?result?元素都將一個列的值映射到一個簡單數據類型(String, int, double, Date 等)的屬性或字段。這兩者之間的唯一不同是,id?元素表示的結果將是對象的標識屬性,例如主鍵,這會在比較對象實例時用到。 這樣可以提高整體的性能,尤其是進行緩存和嵌套結果映射(也就是連接映射)的時候。下面看一下兩個元素都有的一些屬性:
為了以后可能的使用場景,MyBatis 通過內置的 jdbcType 枚舉類型支持下面的 JDBC 類型。
通過id和result元素可以滿足大多數的數據傳輸對象(Data Transfer Object, DTO)以及絕大部分領域模型的要求。但有些情況下你想使用不可變類。 一般來說,很少改變或基本不變的包含引用或數據的表,很適合使用不可變類。 構造方法注入允許你在初始化時為類設置屬性的值,而不用暴露出公有方法。MyBatis 也支持私有屬性和私有 JavaBean 屬性來完成注入,但有一些人更青睞于通過構造方法進行注入。?constructor?元素就是為此而生的。來看一個簡單的構造方法:
public User(Integer id, String username, int age) { }
為了將結果注入構造方法,MyBatis 需要通過某種方式定位相應的構造方法。 在下面的例子中,MyBatis 搜索一個聲明了三個形參的的構造方法,參數類型以?java.lang.Integer,?java.lang.String?和?int?的順序給出。
當你在處理一個帶有多個形參的構造方法時,很容易搞亂 arg 元素的順序。 從版本 3.4.3 開始,可以在指定參數名稱的前提下,以任意順序編寫 arg 元素。 為了通過名稱來引用構造方法參數,你可以添加?@Param?注解,或者使用 '-parameters' 編譯選項并啟用?useActualParamName?選項(默認開啟)來編譯項目。下面是一個等價的例子,盡管函數簽名中第二和第三個形參的順序與 constructor 元素中參數聲明的順序不匹配。
如果名稱和類型的屬性相同,那么可以省略?javaType?。其它元素標簽的屬性和id以及result的是差不多的:
上面直接配置屬性和配置構造器其實就是創建對象實例的兩種不同方法而已,Java中基本也都是使用set方法賦值或者創建對象時直接使用有參數的構造器創建。原理上都差不多。
多對一關聯
再來看一下關聯,數據庫中的關聯關系大致上分為一對多、多對一和多對多三種情況。下面看一個多對一的例子:
上面的關聯使用了association標簽,關聯(association)元素處理“有一個”類型的關系。說簡單些就是在多對一的關系中,查詢多方的其中一個,順便把所對應的一方查出來。 比如,在我們的示例中,一個博客對應一個用戶(作者)。關聯結果映射和其它類型的映射工作方式差不多。 你需要指定目標屬性名以及屬性的javaType(很多時候 MyBatis 可以自己推斷出來,所以不用寫),在必要的情況下你還可以設置 JDBC 類型,如果你想覆蓋獲取結果值的過程,還可以設置類型處理器。
關聯的不同之處是,你需要告訴 MyBatis 如何加載關聯。MyBatis 有兩種不同的方式加載關聯:
嵌套 Select 查詢:通過執行另外一個 SQL 映射語句來加載期望的復雜類型。
嵌套結果映射:使用嵌套的結果映射來處理連接結果的重復子集。
首先,先讓我們來看看這個元素的屬性。你將會發現,和普通的結果映射相比,它只在 select 和 resultMap 屬性上有所不同。
先來看一個關聯的嵌套 Select 查詢的例子,先來看用到的屬性:
再看具體映射文件中SQL的寫法:
就是這么簡單。我們有兩個 select 查詢語句:一個用來加載博客(Blog),另外一個用來加載作者(Author),而且博客的結果映射描述了應該使用?selectAuthor?語句加載它的 author 屬性。其它所有的屬性將會被自動加載,只要它們的列名和屬性名相匹配。
這種方式雖然很簡單,但在大型數據集或大型數據表上表現不佳。這個問題被稱為“N+1 查詢問題”。 概括地講,N+1 查詢問題是這樣子的:
你執行了一個單獨的 SQL 語句來獲取結果的一個列表(就是“+1”)。
對列表返回的每條記錄,你執行一個 select 查詢語句來為每條記錄加載詳細信息(就是“N”)。
這個問題會導致成百上千的 SQL 語句被執行。有時候,我們不希望產生這樣的后果。好消息是,MyBatis 能夠對這樣的查詢進行延遲加載,因此可以將大量語句同時運行的開銷分散開來。 然而,如果你加載記錄列表之后立刻就遍歷列表以獲取嵌套的數據,就會觸發所有的延遲加載查詢,性能可能會變得很糟糕。所以還有另外一種方法。就是關聯的嵌套結果映射。
我們來看關聯的嵌套結果映射的例子,先來看用到的屬性:
之前,你已經看到了一個非常復雜的嵌套關聯的例子。 下面的例子則是一個非常簡單的例子,用于演示嵌套結果映射如何工作。 現在我們將博客表和作者表連接在一起,而不是執行一個獨立的查詢語句,就像這樣:
注意查詢中的連接,以及為確保結果能夠擁有唯一且清晰的名字,我們設置的別名。 這使得進行映射非常簡單。現在我們可以映射這個結果:
在上面的例子中,你可以看到,博客(Blog)作者(author)的關聯元素委托名為 “authorResult” 的結果映射來加載作者對象的實例。
非常重要: id 元素在嵌套結果映射中扮演著非常重要的角色。你應該總是指定一個或多個可以唯一標識結果的屬性。 雖然,即使不指定這個屬性,MyBatis 仍然可以工作,但是會產生嚴重的性能問題。 只需要指定可以唯一標識結果的最少屬性。顯然,你可以選擇主鍵(復合主鍵也可以)。
現在,上面的示例使用了外部的結果映射元素來映射關聯。這使得 Author 的結果映射可以被重用。 然而,如果你不打算重用它,或者你更喜歡將你所有的結果映射放在一個具有描述性的結果映射元素中。 你可以直接將結果映射作為子元素嵌套在內。這里給出使用這種方式的等效例子:
那如果博客(blog)有一個共同作者(co-author)該怎么辦?select 語句看起來會是這樣的:
回憶一下,Author 的結果映射定義如下:
由于結果中的列名與結果映射中的列名不同。你需要指定?columnPrefix?以便重復使用該結果映射來映射 co-author 的結果。
下面來看一下關聯的多結果集(ResultSet)的情況,先看屬性:
從版本 3.2.3 開始,MyBatis 提供了另一種解決 N+1 查詢問題的方法。某些數據庫允許存儲過程返回多個結果集,或一次性執行多個語句,每個語句返回一個結果集。 我們可以利用這個特性,在不使用連接的情況下,只訪問數據庫一次就能獲得相關數據。在例子中,存儲過程執行下面的查詢并返回兩個結果集。第一個結果集會返回博客(Blog)的結果,第二個則返回作者(Author)的結果。
在映射語句中,必須通過?resultSets?屬性為每個結果集指定一個名字,多個名字使用逗號隔開。
現在我們可以指定使用 “authors” 結果集的數據來填充 “author” 關聯:
一對多關聯
我們已經在上面看到了如何處理多對一類型的關聯。但是該怎么處理一對多類型的關聯呢?這就是我們接下來要介紹的。一對多的關聯使用集合(collection )標簽,例如:
集合元素和關聯元素幾乎是一樣的,它們相似的程度之高,以致于沒有必要再介紹集合元素的相似部分。 所以讓我們來關注它們的不同之處吧。我們來繼續上面的示例,一個博客(Blog)只有一個作者(Author)。但一個博客有很多文章(Post)。 在博客類中,這可以用下面的寫法來表示,也就是一方包含多方的通俗做法:
private List<Post> posts;
要像上面這樣,映射嵌套結果集合到一個 List 中,可以使用集合元素。 和關聯元素一樣,我們可以使用嵌套 Select 查詢,或基于連接的嵌套結果映射集合。
首先來看集合的嵌套 Select 查詢,讓我們看看如何使用嵌套 Select 查詢來為博客加載文章。
你可能會立刻注意到幾個不同,但大部分都和我們上面學習過的關聯元素非常相似。 首先,你會注意到我們使用的是集合元素。 接下來你會注意到有一個新的 “ofType” 屬性。這個屬性非常重要,它用來將 JavaBean(或字段)屬性的類型和集合存儲的類型區分開來。?在一般情況下,MyBatis 可以推斷 javaType 屬性,因此并不需要填寫。所以很多時候你可以簡略成:
下面來看集合的嵌套結果映射,現在你可能已經猜到了集合的嵌套結果映射是怎樣工作的——除了新增的 “ofType” 屬性,它和關聯的完全相同。首先, 讓我們看看對應的 SQL 語句:
我們再次連接了博客表和文章表,并且為每一列都賦予了一個有意義的別名,以便映射保持簡單。 要映射博客里面的文章集合,就這么簡單:
再提醒一次,要記得上面 id 元素的重要性,如果你不記得了,請閱讀關聯部分的相關部分。如果你喜歡更詳略的、可重用的結果映射,你可以使用下面的等價形式:
再看集合的多結果集(ResultSet),像關聯元素那樣,我們可以通過執行存儲過程實現,它會執行兩個查詢并返回兩個結果集,一個是博客的結果集,另一個是文章的結果集:
在映射語句中,必須通過?resultSets?屬性為每個結果集指定一個名字,多個名字使用逗號隔開。
我們指定 “posts” 集合將會使用存儲在 “posts” 結果集中的數據進行填充:
注意: 對關聯或集合的映射,并沒有深度、廣度或組合上的要求。但在映射時要留意性能問題。 在探索最佳實踐的過程中,應用的單元測試和性能測試會是你的好幫手。 而 MyBatis 的好處在于,可以在不對你的代碼引入重大變更(如果有)的情況下,允許你之后改變你的想法。
高級關聯和集合映射是一個深度話題。就介紹到這里,配合少許的實踐,你會很快了解全部的用法。
鑒別器
下面介紹一下鑒別器,先來看標簽:
有時候,一個數據庫查詢可能會返回多個不同的結果集(但總體上還是有一定的聯系的)。 鑒別器(discriminator)元素就是被設計來應對這種情況的,另外也能處理其它情況,例如類的繼承層次結構。 鑒別器的概念很好理解——它很像 Java 語言中的 switch 語句。
一個鑒別器的定義需要指定 column 和 javaType 屬性。column 指定了 MyBatis 查詢被比較值的地方。 而 javaType 用來確保使用正確的相等測試(雖然很多情況下字符串的相等測試都可以工作)。例如:
在這個示例中,MyBatis 會從結果集中得到每條記錄,然后比較它的 vehicle type 值。 如果它匹配任意一個鑒別器的 case,就會使用這個 case 指定的結果映射。 這個過程是互斥的,也就是說,剩余的結果映射將被忽略(除非它是擴展的,我們將在稍后討論它)。 如果不能匹配任何一個 case,MyBatis 就只會使用鑒別器塊外定義的結果映射。 所以,如果 carResult 的聲明如下:
那么只有 doorCount 屬性會被加載。這是為了即使鑒別器的 case 之間都能分為完全獨立的一組,盡管和父結果映射可能沒有什么關系。在上面的例子中,我們當然知道 cars 和 vehicles 之間有關系,也就是 Car 是一個 Vehicle。因此,我們希望剩余的屬性也能被加載。而這只需要一個小修改。
現在 vehicleResult 和 carResult 的屬性都會被加載了。可能有人又會覺得映射的外部定義有點太冗長了。 因此,對于那些更喜歡簡潔的映射風格的人來說,還有另一種語法可以選擇。例如:
提示: 請注意,這些都是結果映射,如果你完全不設置任何的 result 元素,MyBatis 將為你自動匹配列和屬性。所以上面的例子大多都要比實際的更復雜。 這也表明,大多數數據庫的復雜度都比較高,我們不太可能一直依賴于這種機制。
自動映射
正如上面所說,在簡單的場景下,MyBatis 可以為你自動映射查詢結果。但如果遇到復雜的場景,你需要構建一個結果映射。 但是在下面,你將看到,你可以混合使用這兩種策略。讓我們深入了解一下自動映射是怎樣工作的。
當自動映射查詢結果時,MyBatis 會獲取結果中返回的列名并在 Java 類中查找相同名字的屬性(忽略大小寫)。 這意味著如果發現了?ID?列和?id?屬性,MyBatis 會將列?ID?的值賦給?id?屬性。
通常數據庫列使用大寫字母組成的單詞命名,單詞間用下劃線分隔;而 Java 屬性一般遵循駝峰命名法約定。為了在這兩種命名方式之間啟用自動映射,需要將?mapUnderscoreToCamelCase?設置為 true。設置位置在mybatis的全局配置文件中,需要在settings標簽下面設置,具體設置如下:
甚至在提供了結果映射后,自動映射也能工作。在這種情況下,對于每一個結果映射,在 ResultSet 出現的列,如果沒有設置手動映射,將被自動映射。在自動映射處理完畢后,再處理手動映射。 在下面的例子中,id?和?userName?列將被自動映射,hashed_password?列將根據配置進行映射。
有三種自動映射等級:
NONE?- 禁用自動映射。僅對手動映射的屬性進行映射。
PARTIAL?- 對除在內部定義了嵌套結果映射(也就是連接的屬性)以外的屬性進行映射
FULL?- 自動映射所有屬性。
默認值是?PARTIAL,這是有原因的。當對連接查詢的結果使用?FULL?時,連接查詢會在同一行中獲取多個不同實體的數據,因此可能導致非預期的映射。 下面的例子將展示這種風險:
在該結果映射中,Blog?和?Author?均將被自動映射。但是注意?Author?有一個?id?屬性,在 ResultSet 中也有一個名為?id?的列,所以 Author 的 id 將填入 Blog 的 id,這可不是你期望的行為。 所以,要謹慎使用?FULL。
無論設置的自動映射等級是哪種,你都可以通過在結果映射上設置?autoMapping?屬性來為指定的結果映射設置啟用/禁用自動映射。
多參數傳參
現在傳參時都是在標簽內設置參數的類型的,這是因為我們在接口定義的時候,大部分情況只傳入了一個參數:
Integer? insert(User user);
如果傳入的參數是多個怎么處理呢?比如:
我們可以把多個參數合并成一個,比如放到map里面,或者專門新建一個實體類存儲這些參數,但是mybatis還提供了一種更簡單的方法,就是在接口這里聲明每個參數:
我們在形參的定義類型前面加了一個注解,表示聲明參數,注解中傳入了一個字符串,表示參數的名字,這個名字可以在SQL語句中使用。下面看一下SQL語句,我們既然在接口這里聲明了參數,那么在SQL標簽里,就不用了再寫parameterType了:
通過這種方式可以傳遞多個參數,前面講的foreach標簽里面給集合定義別名也使用了這個注解,大家可以對比看下,充分了解這個注解的作用。