持續(xù)原創(chuàng)輸出,點(diǎn)擊上方藍(lán)字關(guān)注我吧
image
目錄
- 前言
- 什么是結(jié)果映射排截?
- 如何映射嫌蚤?
- 別名映射
- 駝峰映射
- 配置文件開(kāi)啟駝峰映射
- 配置類中開(kāi)啟駝峰映射
- resultMap映射
- 總結(jié)
- 高級(jí)結(jié)果映射
- 關(guān)聯(lián)(association)
- 例子
- 關(guān)聯(lián)的嵌套 Select 查詢
- 關(guān)聯(lián)的嵌套結(jié)果映射
- 總結(jié)
- 集合collection
- 集合的嵌套 Select 查詢
- 集合的嵌套結(jié)果映射
- 關(guān)聯(lián)(association)
- 總結(jié)
前言
- 上一篇文章介紹了Mybatis基礎(chǔ)的CRUD操作、常用的標(biāo)簽匾寝、屬性等內(nèi)容搬葬,如果對(duì)部分不熟悉的朋友可以看Mybatis入門之基本操作荷腊。
- 本篇文章繼續(xù)講解Mybatis的結(jié)果映射的內(nèi)容艳悔,想要在企業(yè)開(kāi)發(fā)中靈活的使用Mybatis,這部分的內(nèi)容是必須要精通的女仰。
什么是結(jié)果映射猜年?
- 簡(jiǎn)單的來(lái)說(shuō)就是一條
SQL查詢語(yǔ)句
返回的字段如何與Java實(shí)體類
中的屬性相對(duì)應(yīng)抡锈。 - 如下一條SQL語(yǔ)句,查詢患者的用戶id乔外,科室id床三,主治醫(yī)生id:
<select id='selectPatientInfos' resultType='com.xxx.domain.PatientInfo'>
select user_id,dept_id,doc_id from patient_info;
</select>
- Java實(shí)體類
PatientInfo
如下:
@Data
public class PatientInfo{
private String userId;
private String deptId;
private String docId;
}
- 程序員寫(xiě)這條SQL的目的就是想查詢出來(lái)的
user_id
,dept_id
,doc_id
分別賦值給實(shí)體類中的userId
,deptId
,docId
。這就是簡(jiǎn)單的結(jié)果映射杨幼。
如何映射撇簿?
- Myabtis中的結(jié)果映射有很多種方式,下面會(huì)逐一介紹差购。
別名映射
- 這個(gè)簡(jiǎn)單四瘫,保持查詢的SQL返回的字段和Java實(shí)體類一樣即可,比如上面例子的SQL可以寫(xiě)成:
<select id='selectPatientInfos' resultType='com.xxx.domain.PatientInfo'>
select user_id as userId,
dept_id as deptId,
doc_id as docId
from patient_info;
</select>
- 這樣就能和實(shí)體類中的屬性映射成功了欲逃。
駝峰映射
- Mybatis提供了駝峰命名映射的方式找蜜,比如數(shù)據(jù)庫(kù)中的
user_id
這個(gè)字段,能夠自動(dòng)映射到userId
屬性稳析。那么此時(shí)的查詢的SQL變成如下即可:
<select id='selectPatientInfos' resultType='com.xxx.domain.PatientInfo'>
select user_id,dept_id,doc_id from patient_info;
</select>
- 如何開(kāi)啟呢洗做?與SpringBoot整合后開(kāi)啟其實(shí)很簡(jiǎn)單,有兩種方式彰居,一個(gè)是配置文件中開(kāi)啟诚纸,一個(gè)是配置類開(kāi)啟。
配置文件開(kāi)啟駝峰映射
- 只需要在
application.properties
文件中添加如下一行代碼即可:
mybatis.configuration.map-underscore-to-camel-case=true
配置類中開(kāi)啟駝峰映射【簡(jiǎn)單了解陈惰,后續(xù)源碼章節(jié)著重介紹】
- 這種方式需要你對(duì)源碼有一定的了解咬清,上一篇入門教程中有提到,Mybatis與Springboot整合后適配了一個(gè)starter奴潘,那么肯定會(huì)有自動(dòng)配置類旧烧,Mybatis的自動(dòng)配置類是
MybatisAutoConfiguration
,其中有這么一段代碼画髓,如下:
image -
@ConditionalOnMissingBean
這個(gè)注解的意思就是當(dāng)IOC容器中沒(méi)有SqlSessionFactory
這個(gè)Bean對(duì)象這個(gè)配置才會(huì)生效;applyConfiguration(factory)
這行代碼就是創(chuàng)建一個(gè)org.apache.ibatis.session.Configuration
賦值給SqlSessionFactoryBean
掘剪。源碼分析到這,應(yīng)該很清楚了奈虾,無(wú)非就是自己在容器中創(chuàng)建一個(gè)SqlSessionFactory
夺谁,然后設(shè)置屬性即可,如下代碼:
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//設(shè)置數(shù)據(jù)源
sqlSessionFactoryBean.setDataSource(dataSource);
//設(shè)置xml文件的位置
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATOIN));
//創(chuàng)建Configuration
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
// 開(kāi)啟駝峰命名映射
configuration.setMapUnderscoreToCamelCase(true);
configuration.setDefaultFetchSize(100);
configuration.setDefaultStatementTimeout(30);
sqlSessionFactoryBean.setConfiguration(configuration);
//將typehandler注冊(cè)到mybatis
sqlSessionFactoryBean.setTypeHandlers(typeHandlers());
return sqlSessionFactoryBean.getObject();
}
-
注意:如果對(duì)
SqlSessionFactory
沒(méi)有特殊定制肉微,不介意重寫(xiě)匾鸥,因?yàn)檫@會(huì)自動(dòng)覆蓋自動(dòng)配置類中的配置。
resultMap映射
- 什么是
resultMap
碉纳?簡(jiǎn)單的說(shuō)就是一個(gè)類似Map的結(jié)構(gòu)勿负,將數(shù)據(jù)庫(kù)中的字段和JavaBean中的屬性字段對(duì)應(yīng)起來(lái),這樣就能做到一一映射了劳曹。 - 上述的例子使用resultMap又會(huì)怎么寫(xiě)呢奴愉?如下:
<!--創(chuàng)建一個(gè)resultMap映射-->
<resultMap id="patResultMap" type="com.xxx.domain.PatientInfo">
<id property="userId" column="user_id" />
<result property="docId" column="doc_id"/>
<result property="deptId" column="dept_id"/>
</resultMap>
<!--使用resultMap映射結(jié)果到com.xxx.domain.PatientInfo這個(gè)Bean中-->
<select id='selectPatientInfos' resultMap='patResultMap'>
select user_id,dept_id,doc_id from patient_info;
</select>
其實(shí)很簡(jiǎn)單琅摩,就是創(chuàng)建一個(gè)
<resultMap>
,然后<select>
標(biāo)簽指定這個(gè)resultMap即可锭硼。-
<resultMap>
的屬性如下:-
id
:唯一標(biāo)識(shí)這個(gè)resultMap房资,同一個(gè)Mapper.xml中不能重復(fù) -
type
:指定JavaBean的類型,可以是全類名檀头,也可以是別名
-
-
子標(biāo)簽
<result>
的屬性如下:-
column
:SQL返回的字段名稱 -
property
:JavaBean中屬性的名稱 -
javaType
:一個(gè) Java 類的全限定名轰异,或一個(gè)類型別名(關(guān)于內(nèi)置的類型別名,可以參考上面的表格)暑始。 如果你映射到一個(gè) JavaBean溉浙,MyBatis 通常可以推斷類型蒋荚。然而戳稽,如果你映射到的是 HashMap,那么你應(yīng)該明確地指定 javaType 來(lái)保證行為與期望的相一致期升。 -
jdbcType
:JDBC 類型惊奇,所支持的 JDBC 類型參見(jiàn)這個(gè)表格之后的“支持的 JDBC 類型”。 只需要在可能執(zhí)行插入播赁、更新和刪除的且允許空值的列上指定 JDBC 類型颂郎。這是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 編程容为,你需要對(duì)可以為空值的列指定這個(gè)類型乓序。 -
typeHandler
: 這個(gè)屬性值是一個(gè)類型處理器實(shí)現(xiàn)類的全限定名,或者是類型別名坎背。 -
resultMap
:結(jié)果映射的 ID替劈,可以將此關(guān)聯(lián)的嵌套結(jié)果集映射到一個(gè)合適的對(duì)象樹(shù)中。 它可以作為使用額外 select 語(yǔ)句的替代方案得滤。
-
總結(jié)
- 以上列舉了三種映射的方式陨献,分別是別名映射,駝峰映射懂更、
resultMap
映射眨业。 -
你以為這就結(jié)束了?要是世界這么簡(jiǎn)單多好沮协,做夢(mèng)吧龄捡,哈哈!?对荨聘殖!
image
高級(jí)結(jié)果映射
- MyBatis 創(chuàng)建時(shí)的一個(gè)思想是:數(shù)據(jù)庫(kù)不可能永遠(yuǎn)是你所想或所需的那個(gè)樣子。 我們希望每個(gè)數(shù)據(jù)庫(kù)都具備良好的第三范式或 BCNF 范式,可惜它們并不都是那樣就斤。 如果能有一種數(shù)據(jù)庫(kù)映射模式悍募,完美適配所有的應(yīng)用程序蘑辑,那就太好了洋机,但可惜也沒(méi)有。 而 ResultMap 就是 MyBatis 對(duì)這個(gè)問(wèn)題的答案洋魂。
- 我們知道在數(shù)據(jù)庫(kù)的關(guān)系中一對(duì)一绷旗,多對(duì)一,一對(duì)多副砍,多對(duì)多的關(guān)系衔肢,那么這種關(guān)系如何在Mybatis中體現(xiàn)并映射成功呢?
關(guān)聯(lián)(association)
- 關(guān)聯(lián)(association)元素處理有一個(gè)類型的關(guān)系豁翎。 比如角骤,在我們的示例中,一個(gè)員工屬于一個(gè)部門心剥。關(guān)聯(lián)結(jié)果映射和其它類型的映射工作方式差不多邦尊。 你需要指定目標(biāo)屬性名以及屬性的
javaType
(很多時(shí)候 MyBatis 可以自己推斷出來(lái)),在必要的情況下你還可以設(shè)置JDBC
類型优烧,如果你想覆蓋獲取結(jié)果值的過(guò)程蝉揍,還可以設(shè)置類型處理器。 - 關(guān)聯(lián)的不同之處是畦娄,你需要告訴 MyBatis 如何加載關(guān)聯(lián)又沾。MyBatis 有兩種不同的方式加載關(guān)聯(lián):
-
嵌套 Select 查詢
:通過(guò)執(zhí)行另外一個(gè) SQL 映射語(yǔ)句來(lái)加載期望的復(fù)雜類型。 -
嵌套結(jié)果映射
:使用嵌套的結(jié)果映射來(lái)處理連接結(jié)果的重復(fù)子集熙卡。
-
- 首先杖刷,先讓我們來(lái)看看這個(gè)元素的屬性。你將會(huì)發(fā)現(xiàn)驳癌,和普通的結(jié)果映射相比挺勿,它只在
select
和resultMap
屬性上有所不同。-
property
: 映射到列結(jié)果的字段或?qū)傩晕蛊狻H绻脕?lái)匹配的 JavaBean 存在給定名字的屬性不瓶,那么它將會(huì)被使用。 -
javaType
:一個(gè) Java 類的完全限定名灾杰,或一個(gè)類型別名(關(guān)于內(nèi)置的類型別名蚊丐,可以參考上面的表格) -
jdbcType
: JDBC 類型, 只需要在可能執(zhí)行插入艳吠、更新和刪除的且允許空值的列上指定 JDBC 類型 -
typeHandler
:使用這個(gè)屬性麦备,你可以覆蓋默認(rèn)的類型處理器。 這個(gè)屬性值是一個(gè)類型處理器實(shí)現(xiàn)類的完全限定名,或者是類型別名凛篙。 -
column
: 數(shù)據(jù)庫(kù)中的列名黍匾,或者是列的別名。一般情況下呛梆,這和傳遞給resultSet.getString(columnName)
方法的參數(shù)一樣锐涯。 注意:在使用復(fù)合主鍵的時(shí)候,你可以使用column="{prop1=col1,prop2=col2}"
這樣的語(yǔ)法來(lái)指定多個(gè)傳遞給嵌套 Select 查詢語(yǔ)句的列名填物。這會(huì)使得prop1
和prop2
作為參數(shù)對(duì)象纹腌,被設(shè)置為對(duì)應(yīng)嵌套 Select 語(yǔ)句的參數(shù)。 -
select
:用于加載復(fù)雜類型屬性的映射語(yǔ)句的 ID滞磺,它會(huì)從 column 屬性指定的列中檢索數(shù)據(jù)升薯,作為參數(shù)傳遞給目標(biāo) select 語(yǔ)句。 具體請(qǐng)參考下面的例子击困。注意:在使用復(fù)合主鍵的時(shí)候涎劈,你可以使用column="{prop1=col1,prop2=col2}"
這樣的語(yǔ)法來(lái)指定多個(gè)傳遞給嵌套 Select 查詢語(yǔ)句的列名。這會(huì)使得 prop1 和 prop2 作為參數(shù)對(duì)象阅茶,被設(shè)置為對(duì)應(yīng)嵌套 Select 語(yǔ)句的參數(shù)蛛枚。 -
fetchType
:可選的。有效值為lazy
和eager
目派。 指定屬性后坤候,將在映射中忽略全局配置參數(shù)lazyLoadingEnabled
,使用屬性的值企蹭。
-
例子
- 一對(duì)一的關(guān)系比如:一個(gè)員工屬于一個(gè)部門白筹,那么數(shù)據(jù)庫(kù)表就會(huì)在員工表中加一個(gè)部門的id作為邏輯外鍵。
- 創(chuàng)建員工JavaBean
@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer deptId;
//部門
private Department department;
}
- 部門JavaBean
@Data
public class Department {
private Integer id;
private String name;
}
- 那么我們想要查詢所有的用戶信息和其所在的部門信息谅摄,此時(shí)的sql語(yǔ)句為:
select * from user u left join department d on u.department_id=d.id
;徒河。但是我們?cè)趍ybaits中如果使用這條語(yǔ)句查詢,那么返回的結(jié)果類型是什么呢送漠?如果是User類型的顽照,那么查詢結(jié)果返回的還有Department
類型的數(shù)據(jù),那么肯定會(huì)對(duì)應(yīng)不上的闽寡。此時(shí)<resultMap>
來(lái)了代兵,它來(lái)了!!!
關(guān)聯(lián)的嵌套 Select 查詢【可以忽略】
- 查詢員工和所在的部門在Mybatis如何寫(xiě)呢?代碼如下:
<resultMap id="userResult" type="com.xxx.domain.User">
<id column="id" property="id"/>
<result column="password" property="password"/>
<result column="age" property="age"/>
<result column="username" property="username"/>
<result column="dept_id" property="deptId"/>
<!--關(guān)聯(lián)查詢爷狈,select嵌套查詢-->
<association property="department" column="dept_id" javaType="com.xxx.domain.Department" select="selectDept"/>
</resultMap>
<!--查詢員工-->
<select id="selectUser" resultMap="userResult">
SELECT * FROM user WHERE id = #{id}
</select>
<!--查詢部門-->
<select id="selectDept" resultType="com.xxx.domain.Department ">
SELECT * FROM department WHERE ID = #{id}
</select>
- 就是這么簡(jiǎn)單植影,兩個(gè)select語(yǔ)句,一個(gè)用來(lái)加載員工涎永,一個(gè)用來(lái)加載部門思币。
- 這種方式雖然很簡(jiǎn)單鹿响,但在大型數(shù)據(jù)集或大型數(shù)據(jù)表上表現(xiàn)不佳。這個(gè)問(wèn)題被稱為
N+1
查詢問(wèn)題谷饿。 概括地講惶我,N+1 查詢問(wèn)題是這樣子的:- 你執(zhí)行了一個(gè)單獨(dú)的 SQL 語(yǔ)句來(lái)獲取結(jié)果的一個(gè)列表(就是
+1
)。 - 對(duì)列表返回的每條記錄博投,你執(zhí)行一個(gè)
select
查詢語(yǔ)句來(lái)為每條記錄加載詳細(xì)信息(就是N
)绸贡。
- 你執(zhí)行了一個(gè)單獨(dú)的 SQL 語(yǔ)句來(lái)獲取結(jié)果的一個(gè)列表(就是
- 這個(gè)問(wèn)題會(huì)導(dǎo)致成百上千的 SQL 語(yǔ)句被執(zhí)行。有時(shí)候贬堵,我們不希望產(chǎn)生這樣的后果恃轩。
關(guān)聯(lián)的嵌套結(jié)果映射【重點(diǎn)】
-
<association >
標(biāo)簽中還可以直接嵌套結(jié)果映射结洼,此時(shí)的Mybatis的查詢?nèi)缦拢?/li>
<!-- 定義resultMap -->
<resultMap id="UserDepartment" type="com.xxx.domain.User" >
<id column="user_id" property="id"/>
<result column="password" property="password"/>
<result column="age" property="age"/>
<result column="username" property="username"/>
<result column="dept_id" property="deptId"/>
<!--
property: 指定User中對(duì)應(yīng)的部門屬性名稱
javaType: 指定類型黎做,可以是全類名或者別名
-->
<association property="department" javaType="com.xx.domain.Department">
<!--指定Department中的屬性映射,這里也可以使用單獨(dú)拎出來(lái)松忍,然后使用association中的resultMap屬性指定-->
<id column="id" property="id"/>
<result column="dept_name" property="name"/>
</association>
</resultMap>
<!--
resultMap: 指定上面resultMap的id的值
-->
<select id="findUserAndDepartment" resultMap="UserDepartment">
select
u.id as user_id,
u.dept_id,
u.name,
u.password,
u.age,
d.id,
d.name as dept_name
from user u left join department d on u.department_id=d.id
</select>
總結(jié)
- 至此
有一個(gè)
類型的關(guān)聯(lián)已經(jīng)完成了蒸殿,學(xué)會(huì)一個(gè)<association>
使用即能完成。 -
注意: 關(guān)聯(lián)的嵌套 Select 查詢不建議使用鸣峭,
N+1
是個(gè)重大問(wèn)題宏所,雖說(shuō)Mybatis提供了延遲加載的功能,但是仍然不建議使用摊溶,企業(yè)開(kāi)發(fā)中也是不常用的爬骤。
集合collection
- 集合,顧名思義莫换,就是處理
有很多個(gè)
類型的關(guān)聯(lián)霞玄。 - 其中的屬性和
association
中的屬性類似,不再重復(fù)了拉岁。 - 比如這樣一個(gè)例子:查詢一個(gè)部門中的全部員工坷剧,查詢SQL如何寫(xiě)呢?如下:
select * from department d left join user u on u.department_id=d.id;
- 此時(shí)的
User
實(shí)體類如下:
@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer deptId;
}
- 此時(shí)的
Department
實(shí)體類如下:
@Data
public class Department {
private Integer id;
private String name;
private List<User> users;
}
- 和
association
類似喊暖,同樣有兩種方式惫企,我們可以使用嵌套 Select 查詢,或基于連接的嵌套結(jié)果映射集合陵叽。
集合的嵌套 Select 查詢【可以忽略】
- 不太重要狞尔,查詢?nèi)缦拢?/li>
<resultMap id="deptResult" type="com.xxx.domain.Department">
<!--指定Department中的屬性映射,這里也可以使用單獨(dú)拎出來(lái)巩掺,然后使用association中的resultMap屬性指定-->
<id column="id" property="id"/>
<result column="name" property="name"/>
<!--
ofType:指定實(shí)際的JavaBean的全類型或者別名
select:指定嵌套的select查詢
javaType:集合的類型偏序,可以不寫(xiě),Mybatis可以推測(cè)出來(lái)
-->
<collection property="users" javaType="java.util.ArrayList" column="id" ofType="com.xxx.doamin.User" select="selectByDeptId"/>
</resultMap>
<select id="selectDept" resultMap="deptResult">
SELECT * FROM department WHERE ID = #{id}
</select>
<select id="selectByDeptId" resultType="com.xxx.domain.User">
SELECT * FROM user WHERE dept_id = #{id}
</select>
-
注意:這里出現(xiàn)了一個(gè)不同于
association
的屬性ofType
锌半,這個(gè)屬性非常重要禽车,它用來(lái)將 JavaBean(或字段)屬性的類型和集合存儲(chǔ)的類型區(qū)分開(kāi)來(lái)寇漫。
集合的嵌套結(jié)果映射【重點(diǎn)】
- 現(xiàn)在你可能已經(jīng)猜到了集合的嵌套結(jié)果映射是怎樣工作的——除了新增的
ofType
屬性,它和關(guān)聯(lián)的完全相同殉摔。 - 此時(shí)的Mybatis查詢?nèi)缦拢?/li>
<!--部門的resultMap-->
<resultMap id="deptResult" type="com.xxx.domain.Department">
<!--指定Department中的屬性映射州胳,這里也可以使用單獨(dú)拎出來(lái),然后使用association中的resultMap屬性指定-->
<id column="dept_id" property="id"/>
<result column="dept_name" property="name"/>
<!--
ofType:指定實(shí)際的JavaBean的全類型或者別名
resultMap:指定員工的resultMap
-->
<collection property="users" ofType="com.xxx.doamin.User" resultMap='userResult'/>
</resultMap>
<!--員工的resultMap-->
<resultMap id="userResult" type="com.xxx.domain.User">
<id column="user_id" property="id"/>
<result column="password" property="password"/>
<result column="age" property="age"/>
<result column="username" property="username"/>
</resultMap>
<select id="selectDeptById" resultType="com.xxx.domain.Department">
select
d.id as dept_id,
d.name as dept_name,
u.id as user_id,
u.password,
u.name
from department d left join user u on u.department_id=d.id
where d.id=#{id}
</select>
總結(jié)
- 至此Mybatis第二彈之結(jié)果映射已經(jīng)寫(xiě)完了逸月,如果覺(jué)得作者寫(xiě)的不錯(cuò)栓撞,給個(gè)在看關(guān)注一波,后續(xù)還有更多精彩內(nèi)容推出碗硬,請(qǐng)關(guān)注WX公眾號(hào)【碼猿技術(shù)專欄】瓤湘。