Mybatis的映射文件中頂級的標簽并不多,之前有說過select、update、delete、insert、sql等標簽,resultMap在之前的文章也有提過,但是當時也是簡單的提過,其實這個標簽里面的內(nèi)容很多,可簡單可復雜。正常開發(fā)中簡單的基本都在使用,但是涉及到復雜的用的就少啦。
association實現(xiàn)一對一的查詢,collection實現(xiàn)一對多的查詢,discriminator實現(xiàn)鑒別器的功能,也就是根據(jù)不同的條件實現(xiàn)不同的關聯(lián)查詢。為什么說用到這些復雜的內(nèi)容很少呢。以前在項目上性能要求不是很高,對分庫分表概念沒有那么高的時候,采用關聯(lián)查詢其實沒有什么不好,但是現(xiàn)在互聯(lián)網(wǎng)發(fā)展迅速,數(shù)據(jù)量變得龐大,查詢SQL的性能要求也變得更高。關聯(lián)查詢在大數(shù)據(jù)量的時候執(zhí)行效率不高也就格外的凸顯出來了。如果看過阿里的Java開發(fā)手冊可以知道,他們是不推薦關聯(lián)查詢,而是提倡單表操作,如果需要查其他表信息,就根據(jù)條件,操作其他單表。
雖然關聯(lián)查詢操作變少,但是面試的時候還是可以用來問的。不管從哪一個方面來說擴展知識面都不是壞事。
resultMap子標簽之a(chǎn)ssociation標簽
在說association標簽的具體使用前,先說一下兩種關聯(lián)查詢方式。
- 嵌套結果:使用嵌套結果映射來處理重復的聯(lián)合結果的子集
- 嵌套查詢:通過執(zhí)行另外一個 SQL 映射語句來返回預期的復雜類型
咋一看上面的兩個概念很難理解,下面用代碼體現(xiàn)一下。
嵌套結果方式
//用戶類
public class User{
private Integer id;
private String user_name;
private String phone;
private String email;
private Father father;
}
//父親類
public class Father{
private Integer id;
private String name;
private Integer age;
}
<!--用戶表基礎字段的映射關系-->
<resultMap id="userBaseColumnMap" type="com.zdydoit.core.model.User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="phone" column="phone"/>
<result property="email" column="email"/>
</resultMap>
<!-- 嵌套結果方式 -->
<resultMap id="selectUserMap1" extends="userBaseColumnMap" type="com.zdydoit.core.model.User">
<association property="father" javaType="com.zdydoit.core.model.Father" columnPrefix="tf_">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
</association>
</resultMap>
<!--查詢語句-->
<select id="slectUser1" resultMap="selectUserMap1" parameterType="java.lang.Integer">
select
tu.id,
tu.user_name,
tu.phone,
tu.email,
tu.father_id,
tf.id tf_id,
tf.name tf_name,
tf.age tf_age
from t_user tu,t_father tf
where tu.id = #{id} and tu.father_id = tf.id
</select>
用戶表中有一個字段father,對應的是Father實體類,每人都有一個父親,而且是一對一的關系,至于那些大干爹、二干爹這里不做考慮。查詢出來的結果用戶信息的字段自然封裝到User中,那父親的信息就是放在father字段中。
查詢語句對應的resultMap是selectUserMap1,selectUserMap1是繼承userBaseColumnMap,這里繼承可以理解成java里面的繼承,也就是說繼承了父類的字段映射關系。重點看的是association標簽,property屬性對應的是User類中的father字段,表示association內(nèi)的查詢結果是封裝在father字段中,對應的javaType自然就是Father,columnPrefix表示字段的前綴,這里加上前綴是很有必要的,因為在兩個表做關聯(lián)的時候,可能會存在數(shù)據(jù)庫中字段名相同,那么在這里如果不用別名,就會導致兩個字段映射過程混亂。
嵌套查詢方式
這種方式需要關聯(lián)到另一個命名空間,上面的例子是在同一個命名空間com.zdydoit.core.mapper.UserMapper下。這里的另一個命名空間就是com.zdydoit.core.mapper.FatherMapper。
實體類的代碼和上面的一樣,下面只做映射關系的展示。
com.zdydoit.core.mapper.UserMapper命名空間下:
<!-- 嵌套查詢方式 -->
<resultMap id="selectUserMap2" type="com.zdydoit.core.model.User">
<association property="father" column="father_id" select="com.zdydoit.core.mapper.FatherMapper.selectById"/>
</resultMap>
<!-- 查詢語句 -->
<select id="selectUser2" resultMap="selectUserMap2" parameterType="java.lang.Integer">
select
id,
user_name,
phone,
email,
father_id
from t_user
where id = #{id}
</select>
com.zdydoit.core.mapper.FatherMapper命名空間下:
<resultMap id="fatherBaseColumnMap" type="com.zdydoit.core.model.Father">
<id property="id" column="id"/>
<result property="name" column="name" />
<result property="age" column="age" />
</resultMap>
<!-- 單表查詢 -->
<select id="selectById" resultMap="fatherBaseColumnMap">
select
id,
name,
age
from t_father
where id = #{father_id}
</select>
這樣查詢的過程稍微有點繞。首先是對t_user表執(zhí)行單表查詢操作,查詢的結果中有一個字段father_id,然后就是association標簽將這個字段作為條件傳給com.zdydoit.core.mapper.FatherMapper命名空間,執(zhí)行另一個單表查詢操作selectById查詢Father的信息。和之前的區(qū)別就是,之前是一個SQL執(zhí)行關聯(lián)查詢,現(xiàn)在是用多個單表查詢組合得到查詢結果。
這里只有一個查詢條件father_id,如果有多個寫法就按下面的寫法。
<!--
這里旨在說明column多參數(shù)的寫法
'rel_'開頭的值表示SQL語句里面查詢出的字段名
等號左邊的表示在selectInfo中引用的字段名稱
-->
<association property = "father" colum="{id = rel_id,age = rel_age,nickname = rel_nickname}" select="com.zdydoit.mapper.XxxMapper.selectInfo">
association標簽的屬性很多,下面總結一下常用標簽的含義。
-
property屬性,這個屬性是必須的,對應的是實體類中的字段,和result標簽的property屬性是相同含義的。 -
resultMap屬性,association標簽內(nèi)也有id、reuslt標簽,用來做關聯(lián)類的字段映射關系,當然這個關系也可以通過一個reusltMap表示,省去association內(nèi)寫過多的映射關系。這種reusltMap指定可以跨命名空間。 -
column屬性,在嵌套查詢方式的時候使用,用來給嵌套語句查詢傳遞條件,可以傳遞多個。 -
columnPrefix屬性,在嵌套結果方式的時候使用,用來指定別名字段的前綴,可用于防止兩個表結果字段重名問題。 -
select屬性,在嵌套查詢方式的時候使用,用來指定嵌套語句查詢的具體坐標。 -
fetchType屬性,這個屬性有兩個可選值,分別是lazy和eager,分別表示是否懶加載,如果設置為懶加載,在主類中沒有使用到這個關聯(lián)對象,就會少一次關聯(lián)字句查詢的過程,只有使用的時候才去查詢。 -
javaType屬性,指定關聯(lián)對象的具體類型。
resultMap子標簽之collection標簽
collection和association標簽的使用方式是相同的,也包含兩種關聯(lián)查詢方式,唯一不同的就是association只能對應一條記錄,是一對一的關系,使用單個類字段來接收,collection可以對應多條記錄,是一對多的關系,接收使用集合方式。
還有就是collection多了一個ofType字段,這個字段是用來指定集合內(nèi)元素的類型,如下:
private List<Computer> computers;
這個是有ofType對應的類型就是Computer。同樣理解javaType對應的類型就是List。不過這里一般都不會手動去指定javaType,會自動映射到集合中。
resultMap子標簽之discriminator標簽
這個應用場景感覺不多,不過還是可以舉個栗子理解一下的。
在做體檢的時候,男性和女性體檢內(nèi)容是有區(qū)別的?,F(xiàn)在有三張表,分別是人員表、男性體檢報告表、女性體檢報告表。要求查一個人的體檢報告。那很簡單是當這個人是男性的時候,查詢其體檢報告是從男性體檢報告表中查詢,反之就到女性體檢報告表中查詢。實現(xiàn)方式如下:
<!-- 人員基礎信息 -->
<resultMap id="personBaseColumnMap" type="Person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="gender" column="gender"/>
</resultMap>
<!-- 查詢女性體檢報告 -->
<resultMap id="healthReportFemaleMap" extends="personReportMap" type="Person">
<collection property="healthReports" column="id" select="com.zdydoit.mapper.HealthFemaleMapper.selectByPersonId"/>
</resultMap>
<!-- 查詢男性體檢報告 -->
<resultMap id="healthReportMaleMap" extends="personReportMap" type="Person">
<collection property="healthReports" column="id" select="com.zdydoit.mapper.HealthMaleMapper.selectByPersonId"/>
</resultMap>
<!-- 查詢?nèi)藛T信息+體檢報告 -->
<resultMap id="personReportMap" extends="personBaseColumnMap" type="Person">
<discriminator javaType="java.lang.Integer" column="gender">
<case value="1" resultMap="healthReportMaleMap"/>
<case value="2" resultMap="healthReportFemaleMap"/>
</discriminator>
</resultMap>
<!-- 查詢語句 -->
<select id="selectPersonReport" resultMap="personReportMap" parameterType="java.lang.Integer">
select id,name,gender from t_person where id = #{id}
</select>
有幾點需要說一下。
-
case標簽里面結果涉及到兩個報告類型分別是HealthReportMale和HealthReportFemale,但是Person類中可以定義兩個字段分別接收這兩種,也可以用一個字段接收,這個接收字段集合的元素類型應該定義一個父類HealthReport,讓HealthReportMale和HealthReportFemale都繼承這個父類。 -
case標簽內(nèi)有resultType和resultMap兩種指定結果的方式,這里使用的都是resultMap的方式,因為這種方式更優(yōu)。如果使用resultType,只能使用嵌套結果方式,然后又涉及到多張不同的表,代碼量變大,而且是重復的代碼量。這里稍微有點難理解,可以上手寫一下就知道了。 -
resultMap的繼承關系,這里涉及到多個resultMap,select標簽指定的是personReportMap,是直接resultMap,personReportMap繼承personBaseColumnMap,因為人員基礎信息映射關系都在這個personBaseColumnMap中,然后case內(nèi)的healtReportMaleMap和healthReportFemaleMap都是繼承personReportMap。
Overview
習慣性的總結一下。
- 說了兩種關聯(lián)查詢方式,分別是嵌套結果和嵌套查詢,貫穿了三個標簽的使用。
-
association標簽用于一對一的關聯(lián)查詢,主要屬性也做了說明。 -
collection標簽用于一對多的關聯(lián)查詢,沒有具體說,可以參考association標簽的解釋,基本相同。 -
discriminator標簽,使用的過程最為復雜,主要的是要理清resultMap的繼承關系。 - 這些標簽在實際開發(fā)中很少用到,現(xiàn)在都提倡單表操作,簡單高效,兼容分庫分表等。因此只做了解,掃知識盲點,提高面試底氣。但是
resultMap標簽的基本使用是簡單的,而且是極其重要的。
resultMap標簽是很重要的,甚至可以說是極其的重要,但是這些關聯(lián)查詢的功能標簽不是很重要,這個是不相悖的。
resultMap有個很大的優(yōu)點就是解耦合(阿里Java手冊也著重強調(diào))。如果使用自動映射的話,會將數(shù)據(jù)庫列表和實體類的字段名強耦合,當數(shù)據(jù)庫列表發(fā)生修改時,實體類中的字段名也要同步修改。但是使用resultMap指定映射關系,當有數(shù)據(jù)庫列名發(fā)生變化,只要改一下映射中的column值即可,實體類無需任何修改變動。
本文作者:程序猿楊鮑
版權歸作者所有,轉(zhuǎn)載請注明出處