-
Result Maps(結果集)
resultMap 元素是 MyBatis 中最重要最強大的元素。它就是讓你遠離 90%的需要從結果 集中取出數據的 JDBC 代碼的那個東西, 而且在一些情形下允許你做一些 JDBC 不支持的事 情。 事實上, 編寫相似於對復雜語句聯合映射這些等同的代碼, 也許可以跨過上千行的代碼。 ResultMap 的設計就是簡單語句不需要明確的結果映射,而很多復雜語句確實需要描述它們 的關系,你已經看到簡單映射語句的示例了,但沒有明確的 resultMap。比如:
<select id="selectUsers" resultType="map">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
這樣一個語句簡單作用於所有列被自動映射到 HashMap 的鍵上,這由 resultType 屬性 指定。這在很多情況下是有用的,但是 HashMap 不能很好描述一個領域模型。那樣你的應 用程序將會使用 JavaBeans 或 POJOs(Plain Old Java Objects,普通 Java 對象)來作為領域 模型。MyBatis 對兩者都支持。看看下面這個 JavaBean:
package com.someapp.model;
public class User {
private int id;
private String username;
private String hashedPassword;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getHashedPassword() {
return hashedPassword;
}
public void setHashedPassword(String hashedPassword) {
this.hashedPassword = hashedPassword;
}
}
基於 JavaBean 的規范,上面這個類有 3 個屬性:id,username 和 hashedPassword。這些 在 select 語句中會精確匹配到列名。
這樣的一個 JavaBean 可以被映射到結果集,就像映射到 HashMap 一樣簡單:
<select id="selectUsers" resultType="com.someapp.model.User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
這些情況下,MyBatis 會在幕后自動創建一個 ResultMap,基於屬性名來映射列到 JavaBean 的屬性上。如果列名沒有精確匹配,你可以在列名上使用 select 字句的別名(一個 基本的 SQL 特性)來匹配標簽。比如:
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from some_table
where id = #{id}
</select>
ResultMap 最優秀的地方你已經了解了很多了,但是你還沒有真正的看到一個。這些簡 單的示例不需要比你看到的更多東西。 只是出於示例的原因, 讓我們來看看最后一個示例中 外部的 resultMap 是什么樣子的,這也是解決列名不匹配的另外一種方式。
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
引用它的語句使用 resultMap 屬性就行了(注意我們去掉了 resultType 屬性)。比如:
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
下面是 resultMap 元素的概念視圖
-
constructor - 類在實例化時,用來注入結果到構造方法中
- idArg - ID 參數;標記結果作為 ID 可以幫助提高整體效能
- arg - 注入到構造方法的一個普通結果
- id – 一個 ID 結果;標記結果作為 ID 可以幫助提高整體效能
- result – 注入到字段或 JavaBean 屬性的普通結果
-
association – 一個復雜的類型關聯;許多結果將包成這種類型
- 嵌入結果映射 – 結果映射自身的關聯,或者參考一個
-
collection – 復雜類型的集
- 嵌入結果映射 – 結果映射自身的集,或者參考一個
-
discriminator – 使用結果值來決定使用哪個結果映射
-
case – 基於某些值的結果映射
- 嵌入結果映射 – 這種情形結果也映射它本身,因此可以包含很多相 同的元素,或者它可以參照一個外部的結果映射。
-
- resultMap元素屬性和描述:
-
屬性 |
描述 |
id |
當前命名空間中的一個唯一標識,用於標識一個result map. |
type |
類的全限定名, 或者一個類型別名 (內置的別名可以參考上面的表格). |
autoMapping |
如果設置這個屬性,MyBatis將會為這個ResultMap開啟或者關閉自動映射。這個屬性會覆蓋全局的屬性autoMappingBehavior。默認值為:unset。 |
-
id & result元素屬性和描述:
這些是結果映射最基本內容。id 和 result 都映射一個單獨列的值到簡單數據類型(字符 串,整型,雙精度浮點數,日期等)的單獨屬性或字段。 這兩者之間的唯一不同是 id 表示的結果將是當比較對象實例時用到的標識屬性。這幫助來改進整體表現,特別是緩存和嵌入結果映射(也就是聯合映射) ,屬性描述:
屬性 |
描述 |
property |
映射到列結果的字段或屬性。如果匹配的是存在的,和給定名稱相同 的 JavaBeans 的屬性,那么就會使用。否則 MyBatis 將會尋找給定名稱 property 的字段。這兩種情形你可以使用通常點式的復雜屬性導航。比如,你 可以這樣映射一些東西: "username" ,或者映射到一些復雜的東西: "address.street.number" 。 |
column |
從數據庫中得到的列名,或者是列名的重命名標簽。這也是通常和會 傳遞給 resultSet.getString(columnName)方法參數中相同的字符串。 |
javaType |
一個 Java 類的完全限定名,或一個類型別名(參考上面內建類型別名 的列表) 。如果你映射到一個 JavaBean,MyBatis 通常可以斷定類型。 然而,如果你映射到的是 HashMap,那么你應該明確地指定 javaType 來保證所需的行為。 |
jdbcType |
在這個表格之后的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅 僅需要對插入,更新和刪除操作可能為空的列進行處理。這是 JDBC jdbcType 的需要,而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定 這個類型-但僅僅對可能為空的值。 |
typeHandler |
我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默認的類型處理器。這個屬性值是類的完全限定名或者是一個類型處理 器的實現,或者是類型別名。 |
為了未來的參考,MyBatis 通過包含的 jdbcType 枚舉型,支持下面的 JDBC 類型:
BIT |
FLOAT |
CHAR |
TIMESTAMP |
OTHER |
UNDEFINED |
TINYINT |
REAL |
VARCHAR |
BINARY |
BLOB |
NVARCHAR |
SMALLINT |
DOUBLE |
LONGVARCHAR |
VARBINARY |
CLOB |
NCHAR |
INTEGER |
NUMERIC |
DATE |
LONGVARBINARY |
BOOLEAN |
NCLOB |
BIGINT |
DECIMAL |
TIME |
NULL |
CURSOR |
ARRAY |
-
constructor 元素屬性和描述:
對於大多數數據傳輸對象(Data Transfer Object,DTO)類型,屬性可以起作用,而且像 你絕大多數的領域模型, 指令也許是你想使用一成不變的類的地方。 通常包含引用或查詢數 據的表很少或基本不變的話對一成不變的類來說是合適的。 構造方法注入允許你在初始化時 為類設置屬性的值,而不用暴露出公有方法。MyBatis 也支持私有屬性和私有 JavaBeans 屬 性來達到這個目的,但是一些人更青睞構造方法注入。構造方法元素支持這個。
屬性 |
描述 |
column |
來自數據庫的類名,或重命名的列標簽。這和通常傳遞給 resultSet.getString(columnName)方法的字符串是相同的。 |
javaType |
一個 Java 類的完全限定名,或一個類型別名(參考上面內建類型別名的列表)。 如果你映射到一個 JavaBean,MyBatis 通常可以斷定類型。然而,如 果你映射到的是 HashMap,那么你應該明確地指定 javaType 來保證所需的 行為。 |
jdbcType |
在這個表格之前的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅僅 需要對插入, 更新和刪除操作可能為空的列進行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定這個類型-但 僅僅對可能為空的值。 |
typeHandler |
我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默認的 類型處理器。 這個屬性值是類的完全限定名或者是一個類型處理器的實現, 或者是類型別名。 |
select |
用於加載復雜類型屬性的映射語句的ID,從column中檢索出來的數據,將作為此select語句的參數。具體請參考Association標簽。 |
resultMap |
ResultMap的ID,可以將嵌套的結果集映射到一個合適的對象樹中,功能和select屬性相似,它可以實現將多表連接操作的結果映射成一個單一的ResultSet。這樣的ResultSet將會將包含重復或部分數據重復的結果集正確的映射到嵌套的對象樹中。為了實現它, MyBatis允許你 "串聯" ResultMap,以便解決嵌套結果集的問題。想了解更多內容,請參考下面的Association元素。 |
注意:如果出現 java.lang.NoSuchMethodException 異常,無法找到構造函數,首先檢查構造函數的參數類型和順序,如果還有問題可以將 long、int 等替換為 Long、Integer 類
-
association (關聯)
關聯元素處理"有一個"類型的關系。比如,在我們的示例中,一個博客有一個用戶,關聯映射就工作於這種結果之上。你指定了目標屬性,來獲取值的列,屬性的 java 類型(很 多情況下 MyBatis 可以自己算出來),如果需要的話還有 jdbc 類型,如果你想覆蓋或獲取的結果值還需要類型控制器。
關聯中不同的是你需要告訴 MyBatis 如何加載關聯。MyBatis 在這方面會有兩種不同的 方式:
- 嵌套查詢:通過執行另外一個 SQL 映射語句來返回預期的復雜類型。
- 嵌套結果:使用嵌套結果映射來處理重復的聯合結果的子集。首先,然讓我們來查看這個元素的屬性。所有的你都會看到,它和普通的只由 select 和 resultMap 屬性的結果映射不同。
公共屬性如下:
屬性 |
描述 |
property |
映射到列結果的字段或屬性。如果匹配的是存在的,和給定名稱相同的 property JavaBeans 的屬性, 那么就會使用。 否則 MyBatis 將會尋找給定名稱的字段。 這兩種情形你可以使用通常點式的復雜屬性導航。比如,你可以這樣映射 一 些 東 西 :" username ", 或 者 映 射 到 一 些 復 雜 的 東 西 : "address.street.number" 。 |
javaType |
一個 Java 類的完全限定名,或一個類型別名(參考上面內建類型別名的列 表) 。如果你映射到一個 JavaBean,MyBatis 通常可以斷定類型。然而,如 javaType 果你映射到的是 HashMap,那么你應該明確地指定 javaType 來保證所需的 行為。 |
jdbcType |
在這個表格之前的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅僅 需要對插入, 更新和刪除操作可能為空的列進行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定這個類型-但 僅僅對可能為空的值。 |
typeHandler |
我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默認的 typeHandler 類型處理器。 這個屬性值是類的完全限定名或者是一個類型處理器的實現, 或者是類型別名。 |
- 嵌套查詢
屬性 |
描述 |
column |
來自數據庫的類名或重命名的列標簽。這和通常傳遞給 resultSet.getString(columnName)方法的字符串是相同的。 column 注 意 : 要 處 理 復 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= " {prop1=col1,prop2=col2} " 這種語法來傳遞給嵌套查詢語 句。這會引起 prop1 和 prop2 以參數對象形式來設置給目標嵌套查詢語句。 |
select |
另外一個映射語句的 ID,可以加載這個屬性映射需要的復雜類型。獲取的 在列屬性中指定的列的值將被傳遞給目標 select 語句作為參數。表格后面 有一個詳細的示例。 select 注 意 : 要 處 理 復 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= " {prop1=col1,prop2=col2} " 這種語法來傳遞給嵌套查詢語 句。這會引起 prop1 和 prop2 以參數對象形式來設置給目標嵌套查詢語句。 |
fetchType |
可選的。有效值為 lazy和eager。 如果使用了,它將取代全局配置參數lazyLoadingEnabled。 |
這種方式很簡單, 但是對於大型數據集合和列表將不會表現很好。 問題就是我們熟知的 "N+1 查詢問題"。概括地講,N+1 查詢問題可以是這樣引起的:
- 你執行了一個單獨的 SQL 語句來獲取結果列表(就是"+1")。
- 對返回的每條記錄,你執行了一個查詢語句來為每個加載細節(就是"N")。
這個問題會導致成百上千的 SQL 語句被執行。這通常不是期望的,MyBatis 能延遲加載這樣的查詢就是一個好處,因此你可以分散這些語句同時運行的消 耗。然而,如果你加載一個列表,之后迅速迭代來訪問嵌套的數據,你會調用所有的延遲加 載,這樣的行為可能是很糟糕的,示例代碼如下:
<!-- 查詢實體的方法Map,其結果映射為 userInfoResult -->
<select id="getModel" parameterType="long" resultMap="userInfoResult">
SELECT
User_ID as "userId",
Name as "name",
Realname as "realname",
Email as "email",
Phone as "phone",
Create_Time as "createTime"
FROM User_Info
WHERE User_ID = #{userId}
</select>
<!-- userInfoResult 結果映射,在 Model 中增加了屬性 userAccount 類型為 UserAccountModel -->
<resultMap id="userInfoResult" type="userInfoModel">
<!--
userInfoModel 有屬性 userAccount 其關系列 column 設置為 UserAccountModel 類的 userId 屬性通過該
屬性進行一對一關系,userAccount類的數據來源為 select 配置的查詢結果,fetchType 設置了延遲加載
-->
<association property="userAccount" column="userId" javaType="userAccountModel" select="selectUserAccount" fetchType="lazy"/>
</resultMap>
<!-- 獲取 UserAccountModel 類的查詢實體 SQL -->
<select id="selectUserAccount" resultType="userAccountModel" >
SELECT
User_ID as "userId",
Login_Account as "loginAccount",
Login_Password as "loginPassword",
Last_Login_Time as "lastLoginTime",
Create_Time as "createTime"
FROM User_Account
WHERE User_ID = #{userId}
</select>
- 嵌套結果
屬性 |
描述 |
resultMap |
這是結果映射的 ID,可以映射關聯的嵌套結果到一個合適的對象圖中。這 是一種替代方法來調用另外一個查詢語句。這允許你聯合多個表來合成到 resultMap 一個單獨的結果集。這樣的結果集可能包含重復,數據的重復組需要被分解,合理映射到一個嵌套的對象圖。為了使它變得容易,MyBatis 讓你"鏈接"結果映射,來處理嵌套結果。 |
columnPrefix |
當連接多表時,你將不得不使用列別名來避免ResultSet中的重復列名。指定columnPrefix允許你映射列名到一個外部的結果集中。 請看后面的例子。 |
notNullColumn |
默認情況下,子對象僅在至少一個列映射到其屬性非空時才創建。 通過對這個屬性指定非空的列將改變默認行為,這樣做之后Mybatis將僅在這些列非空時才創建一個子對象。 可以指定多個列名,使用逗號分隔。默認值:未設置(unset)。 |
autoMapping |
如果使用了,當映射結果到當前屬性時,Mybatis將啟用或者禁用自動映射。 該屬性覆蓋全局的自動映射行為。 注意它對外部結果集無影響,所以在select or resultMap屬性中這個是毫無意義的。 默認值:未設置(unset)。 |
配置示例:
<select id="getFullModel" parameterType="long" resultMap="fullResult">
SELECT
ui.User_ID as "userId",
ui.Name as "name",
ui.Realname as "realname",
ui.Email as "email",
ui.Phone as "phone",
ui.Create_Time as "createTime",
ua.User_ID as "ua_userId",
ua.Login_Account as "ua_loginAccount",
ua.Login_Password as "ua_loginPassword",
ua.Create_Time as "ua_createTime"
FROM User_Info ui,User_Account ua
WHERE ui.User_ID = ua.User_ID AND ui.User_ID = #{userId}
</select>
<!-- 多表關聯查詢結果必須顯示聲明屬性和列映射關系,autoMappingBehavior 配置默認值為 PARTIAL 不會自動映射嵌套結果集 -->
<resultMap id="fullResult" type="userInfoModel">
<id property="userId" column="userId"/>
<result property="name" column="name"/>
<result property="realname" column="realname"/>
<result property="email" column="email"/>
<result property="phone" column="phone"/>
<result property="createTime" column="createTime"/>
<association property="userAccount" column="userId" javaType="userAccountModel" columnPrefix="ua_" >
<id property="userId" column="userId"/>
<result property="loginAccount" column="loginAccount"/>
<result property="loginPassword" column="loginPassword"/>
<result property="createTime" column="createTime"/>
</association>
</resultMap>
-
collection (集合)
前面所的關聯都是一對一的關系,集合表示的是一對多的關系,其屬性也和集合是一致的,集合也和關聯一樣存在嵌套查詢和嵌套結果二種類型。需要在實體類中定義集合屬性,類定義如下:
package org.mybatisExamples.simple;
import java.util.Date;
import java.util.List;
public class UserInfoModel {
private long userId;
private String name;
private String realname;
private String email;
private String phone;
private Date createTime;
// 定義集合類型屬性
private List<UserCertifyRecordModel> certifys;
/* 省略getter和setter方法*/
}
-
嵌套查詢
- Mapper文件增加集合關聯
<!-- 集合嵌套查詢 Begin -->
<select id="getModelAndCertify" parameterType="Long" resultMap="certifyResult">
SELECT
User_ID as userId,
Name as name,
Realname as realname,
Email as email,
Phone as phone,
Create_Time as createTime
FROM User_Info
WHERE User_ID = #{userId}
</select>
<!-- 結果集增加集合關聯,ofType 設置集合元素類型 select 設置嵌入查詢 -->
<resultMap id="certifyResult" type="userInfoModel">
<collection property="certifys" column="userId" ofType="userCertifyRecordModel" select="getCertifyByUserId"/>
</resultMap>
<select id="getCertifyByUserId" parameterType="Long" resultType="userCertifyRecordModel">
<![CDATA[
SELECT
Record_ID as recordId,
User_ID as userId,
Certify_Type as certifyType,
Verify_Code as vertifyCode,
Verify_Expire_Time as verifyExpireTime,
Status as status,
Deal_Time as dealTime,
Creat_Time as createTime
FROM User_Certify_Record
WHERE User_ID = #{userId}]]>
</select>
-
嵌套結果
<select id="getModelAndCertifyResultMap" parameterType="long" resultMap="userInfoCertifyCollection">
SELECT
ui.User_ID as userId,
ui.Name as name,
ui.Realname as realname,
ui.Email as email,
ui.Phone as phone,
ui.Create_Time as createTime,
ucr.Record_ID as ucr_recordId,
ucr.User_ID as ucr_userId,
ucr.Certify_Type as ucr_certifyType,
ucr.Verify_Code as ucr_verifyCode,
ucr.Verify_Expire_Time as ucr_verifyExpireTime,
ucr.Status as ucr_status,
ucr.Deal_Time as ucr_dealTime,
ucr.Creat_Time as ucr_createTime
FROM User_Info ui,User_Certify_Record ucr
WHERE ui.User_ID = ucr.User_ID and ui.User_ID = #{userId}
</select>
<!--
必須顯示聲明屬性和列映射關系,autoMappingBehavior 配置默認值為 PARTIAL 不會自動映射嵌套結果集
-->
<resultMap id="userInfoCertifyCollection" type="userInfoModel">
<id property="userId" column="userId"/>
<result property="name" column="name"/>
<result property="realname" column="realname"/>
<result property="email" column="email"/>
<result property="phone" column="phone"/>
<collection property="certifys" column="userId" ofType="userCertifyRecordModel" columnPrefix="ucr_">
<id property="recordId" column="recordId"/>
<result property="userId" column="userId"/>
<result property="certifyType" column="certifyType"/>
<result property="verifyCode" column="verifyCode"/>
<result property="verifyExpireTime" column="verifyExpireTime"/>
<result property="status" column="status"/>
<result property="dealTime" column="dealTime"/>
<result property="createTime" column="createTime"/>
</collection>
</resultMap>
-
-
discriminator (鑒別器)
有時一個單獨的數據庫查詢也許返回很多不同 (但是希望有些關聯) 數據類型的結果集。 鑒別器元素就是被設計來處理這個情況的, 還有包括類的繼承層次結構。 鑒別器非常容易理 解,因為它的表現很像 Java 語言中的 switch 語句。定義鑒別器指定了 column 和 javaType 屬性。 列是 MyBatis 查找比較值的地方。 JavaType 是需要被用來保證等價測試的合適類型,示例如下:
-
創建表
CREATE TABLE Car (
Record_ID BIGINT NOT NULL,
Car_Type VARCHAR(1) DEFAULT NULL,
Door_Size INT(11) DEFAULT NULL,
Box_Size INT(11) DEFAULT NULL,
Color VARCHAR(20) DEFAULT NULL,
PRIMARY KEY (Record_ID)
);
insert into Car values (1,'C',2,null,'紅色');
insert into Car values (2,'C',4,null,'黑色');
insert into Car values (3,'T',null,1,'藍色');
insert into Car values (4,'T',null,2,'藍色');
commit;
-
實體類
-
基類
package org.mybatisExamples.simple;
public class VehicleModel {
private int recordId;
private String carType;
private String color;
/* 省略setter和getter方法 */
}
-
子類
package org.mybatisExamples.simple;
public class CarModel extends VehicleModel {
private int doorSize;
public int getDoorSize() {
return doorSize;
}
public void setDoorSize(int doorSize) {
this.doorSize = doorSize;
}
}
package org.mybatisExamples.simple;
public class TruckModel extends VehicleModel {
private int boxSize;
public int getBoxSize() {
return boxSize;
}
public void setBoxSize(int boxSize) {
this.boxSize = boxSize;
}
}
-
-
Mapper 類
package org.mybatisExamples.simple;
import java.util.List;
public interface VehicleMapper {
List<VehicleModel> findAll();
}
-
Mapper 配置
<resultMap id="vehicleMap" type="vehicleModel">
<id property="id" column="id" />
<result property="color" column="color" />
<result property="carType" column="Car_Type"/>
<!-- 鑒別器使用 Type 列,其類型為 String -->
<discriminator javaType="java.lang.String" column="type">
<!-- Type 列值為 T 時,返回對象結果類型為配置的 truckModel -->
<case value="T" resultType="truckModel">
<result property="boxSize" column="boxsize" />
</case>
<!-- Type 列值為 C 時,返回對象結果類型為配置的 carModel -->
<case value="C" resultType="carModel">
<result property="doorSize" column="doorsize" />
</case>
</discriminator>
</resultMap>
<select id="findAll" resultMap="vehicleMap">
select * from Car
</select>
-
-
繼承 ResultMap
我們可以從從另外一個<resultMap>,繼承出一個新的<resultMap>,這樣,原先的屬性映射可以繼承過來,以實現新的ResultMap,示例代碼如下:
<resultMap type="Student" id="StudentResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="phone" column="phone" />
</resultMap>
<resultMap type="Student" id="StudentWithAddressResult" extends="StudentResult">
<result property="address.addrId" column="addr_id" />
<result property="address.street" column="street" />
<result property="address.city" column="city" />
<result property="address.state" column="state" />
<result property="address.zip" column="zip" />
<result property="address.country" column="country" />
</resultMap>
我們可以使用圓點記法為內嵌的對象的屬性賦值,如果你只想映射Student數據,你可以使用id為StudentResult的resultMap,如下所示:
<select id="findStudentById" parameterType="int" resultMap="StudentResult">
SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}
</select>
如果你想將映射Student數據和Address數據,你可以使用id為StudentWithAddressResult的resultMap:
<select id="selectStudentWithAddress" parameterType="int" resultMap="StudentWithAddressResult">
SELECT STUD_ID, NAME, EMAIL, PHONE, A.ADDR_ID, STREET, CITY,
STATE, ZIP, COUNTRY
FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON
S.ADDR_ID=A.ADDR_ID
WHERE STUD_ID=#{studId}
</select>