目錄
- 前言
- 什麼是結果對映?
- 如何對映?
- 別名對映
- 駝峰對映
- 配置檔案開啟駝峰對映
- 配置類中開啟駝峰對映
- resultMap對映
- 總結
- 高階結果對映
- 關聯(association)
- 例子
- 關聯的巢狀 Select 查詢
- 關聯的巢狀結果對映
- 總結
- 集合collection
- 集合的巢狀 Select 查詢
- 集合的巢狀結果對映
- 關聯(association)
- 總結
前言
- 上一篇文章介紹了Mybatis基礎的CRUD操作、常用的標籤、屬性等內容,如果對部分不熟悉的朋友可以看Mybatis入門之基本操作。
- 本篇文章繼續講解Mybatis的結果對映的內容,想要在企業開發中靈活的使用Mybatis,這部分的內容是必須要精通的。
什麼是結果對映?
- 簡單的來說就是一條
SQL查詢語句
返回的欄位如何與Java實體類
中的屬性相對應。 - 如下一條SQL語句,查詢患者的使用者id,科室id,主治醫生id:
<select id='selectPatientInfos' resultType='com.xxx.domain.PatientInfo'>
select user_id,dept_id,doc_id from patient_info;
</select>
- Java實體類
PatientInfo
如下:
@Data
public class PatientInfo{
private String userId;
private String deptId;
private String docId;
}
- 程式設計師寫這條SQL的目的就是想查詢出來的
user_id
,dept_id
,doc_id
分別賦值給實體類中的userId
,deptId
,docId
。這就是簡單的結果對映。
如何對映?
- Myabtis中的結果對映有很多種方式,下面會逐一介紹。
別名對映
- 這個簡單,保持查詢的SQL返回的欄位和Java實體類一樣即可,比如上面例子的SQL可以寫成:
<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>
- 這樣就能和實體類中的屬性對映成功了。
駝峰對映
- Mybatis提供了駝峰命名對映的方式,比如資料庫中的
user_id
這個欄位,能夠自動對映到userId
屬性。那麼此時的查詢的SQL變成如下即可:
<select id='selectPatientInfos' resultType='com.xxx.domain.PatientInfo'>
select user_id,dept_id,doc_id from patient_info;
</select>
- 如何開啟呢?與SpringBoot整合後開啟其實很簡單,有兩種方式,一個是配置檔案中開啟,一個是配置類開啟。
配置檔案開啟駝峰對映
- 只需要在
application.properties
檔案中新增如下一行程式碼即可:
mybatis.configuration.map-underscore-to-camel-case=true
配置類中開啟駝峰對映【簡單瞭解,後續原始碼章節著重介紹】
- 這種方式需要你對原始碼有一定的瞭解,上一篇入門教程中有提到,Mybatis與Springboot整合後適配了一個starter,那麼肯定會有自動配置類,Mybatis的自動配置類是
MybatisAutoConfiguration
,其中有這麼一段程式碼,如下:
@ConditionalOnMissingBean
這個註解的意思就是當IOC容器中沒有SqlSessionFactory
這個Bean物件這個配置才會生效;applyConfiguration(factory)
這行程式碼就是建立一個org.apache.ibatis.session.Configuration
賦值給SqlSessionFactoryBean
。原始碼分析到這,應該很清楚了,無非就是自己在容器中建立一個SqlSessionFactory
,然後設定屬性即可,如下程式碼:
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//設定資料來源
sqlSessionFactoryBean.setDataSource(dataSource);
//設定xml檔案的位置
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATOIN));
//建立Configuration
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
// 開啟駝峰命名對映
configuration.setMapUnderscoreToCamelCase(true);
configuration.setDefaultFetchSize(100);
configuration.setDefaultStatementTimeout(30);
sqlSessionFactoryBean.setConfiguration(configuration);
//將typehandler註冊到mybatis
sqlSessionFactoryBean.setTypeHandlers(typeHandlers());
return sqlSessionFactoryBean.getObject();
}
- 注意:如果對
SqlSessionFactory
沒有特殊定製,不介意重寫,因為這會自動覆蓋自動配置類中的配置。
resultMap對映
- 什麼是
resultMap
?簡單的說就是一個類似Map的結構,將資料庫中的欄位和JavaBean中的屬性欄位對應起來,這樣就能做到一一對映了。 - 上述的例子使用resultMap又會怎麼寫呢?如下:
<!--建立一個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對映結果到com.xxx.domain.PatientInfo這個Bean中-->
<select id='selectPatientInfos' resultMap='patResultMap'>
select user_id,dept_id,doc_id from patient_info;
</select>
-
其實很簡單,就是建立一個
<resultMap>
,然後<select>
標籤指定這個resultMap即可。 -
<resultMap>
的屬性如下:id
:唯一標識這個resultMap,同一個Mapper.xml中不能重複type
:指定JavaBean的型別,可以是全類名,也可以是別名
-
子標籤
<result>
的屬性如下:column
:SQL返回的欄位名稱property
:JavaBean中屬性的名稱javaType
:一個 Java 類的全限定名,或一個型別別名(關於內建的型別別名,可以參考上面的表格)。 如果你對映到一個 JavaBean,MyBatis 通常可以推斷型別。然而,如果你對映到的是 HashMap,那麼你應該明確地指定 javaType 來保證行為與期望的相一致。jdbcType
:JDBC 型別,所支援的 JDBC 型別參見這個表格之後的“支援的 JDBC 型別”。 只需要在可能執行插入、更新和刪除的且允許空值的列上指定 JDBC 型別。這是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 程式設計,你需要對可以為空值的列指定這個型別。typeHandler
: 這個屬性值是一個型別處理器實現類的全限定名,或者是型別別名。resultMap
:結果對映的 ID,可以將此關聯的巢狀結果集對映到一個合適的物件樹中。 它可以作為使用額外 select 語句的替代方案。
總結
- 以上列舉了三種對映的方式,分別是別名對映,駝峰對映、
resultMap
對映。 - 你以為這就結束了?要是世界這麼簡單多好,做夢吧,哈哈!!!
高階結果對映
- MyBatis 建立時的一個思想是:資料庫不可能永遠是你所想或所需的那個樣子。 我們希望每個資料庫都具備良好的第三正規化或 BCNF 正規化,可惜它們並不都是那樣。 如果能有一種資料庫對映模式,完美適配所有的應用程式,那就太好了,但可惜也沒有。 而 ResultMap 就是 MyBatis 對這個問題的答案。
- 我們知道在資料庫的關係中一對一,多對一,一對多,多對多的關係,那麼這種關係如何在Mybatis中體現並對映成功呢?
關聯(association)
- 關聯(association)元素處理有一個型別的關係。 比如,在我們的示例中,一個員工屬於一個部門。關聯結果對映和其它型別的對映工作方式差不多。 你需要指定目標屬性名以及屬性的
javaType
(很多時候 MyBatis 可以自己推斷出來),在必要的情況下你還可以設定JDBC
型別,如果你想覆蓋獲取結果值的過程,還可以設定型別處理器。 - 關聯的不同之處是,你需要告訴 MyBatis 如何載入關聯。MyBatis 有兩種不同的方式載入關聯:
巢狀 Select 查詢
:通過執行另外一個 SQL 對映語句來載入期望的複雜型別。巢狀結果對映
:使用巢狀的結果對映來處理連線結果的重複子集。
- 首先,先讓我們來看看這個元素的屬性。你將會發現,和普通的結果對映相比,它只在
select
和resultMap
屬性上有所不同。property
: 對映到列結果的欄位或屬性。如果用來匹配的 JavaBean 存在給定名字的屬性,那麼它將會被使用。javaType
:一個 Java 類的完全限定名,或一個型別別名(關於內建的型別別名,可以參考上面的表格)
jdbcType
: JDBC 型別, 只需要在可能執行插入、更新和刪除的且允許空值的列上指定 JDBC 型別typeHandler
:使用這個屬性,你可以覆蓋預設的型別處理器。 這個屬性值是一個型別處理器實現類的完全限定名,或者是型別別名。
column
: 資料庫中的列名,或者是列的別名。一般情況下,這和傳遞給resultSet.getString(columnName)
方法的引數一樣。 注意:在使用複合主鍵的時候,你可以使用column="{prop1=col1,prop2=col2}"
這樣的語法來指定多個傳遞給巢狀 Select 查詢語句的列名。這會使得prop1
和prop2
作為引數物件,被設定為對應巢狀 Select 語句的引數。select
:用於載入複雜型別屬性的對映語句的 ID,它會從 column 屬性指定的列中檢索資料,作為引數傳遞給目標 select 語句。 具體請參考下面的例子。注意:在使用複合主鍵的時候,你可以使用column="{prop1=col1,prop2=col2}"
這樣的語法來指定多個傳遞給巢狀 Select 查詢語句的列名。這會使得 prop1 和 prop2 作為引數物件,被設定為對應巢狀 Select 語句的引數。fetchType
:可選的。有效值為lazy
和eager
。 指定屬性後,將在對映中忽略全域性配置引數lazyLoadingEnabled
,使用屬性的值。
例子
- 一對一的關係比如:一個員工屬於一個部門,那麼資料庫表就會在員工表中加一個部門的id作為邏輯外來鍵。
- 建立員工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;
}
- 那麼我們想要查詢所有的使用者資訊和其所在的部門資訊,此時的sql語句為:
select * from user u left join department d on u.department_id=d.id
;。但是我們在mybaits中如果使用這條語句查詢,那麼返回的結果型別是什麼呢?如果是User型別的,那麼查詢結果返回的還有Department
型別的資料,那麼肯定會對應不上的。此時<resultMap>
來了,它來了!!!
關聯的巢狀 Select 查詢【可以忽略】
- 查詢員工和所在的部門在Mybatis如何寫呢?程式碼如下:
<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"/>
<!--關聯查詢,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>
- 就是這麼簡單,兩個select語句,一個用來載入員工,一個用來載入部門。
- 這種方式雖然很簡單,但在大型資料集或大型資料表上表現不佳。這個問題被稱為
N+1
查詢問題。 概括地講,N+1 查詢問題是這樣子的:- 你執行了一個單獨的 SQL 語句來獲取結果的一個列表(就是
+1
)。 - 對列表返回的每條記錄,你執行一個
select
查詢語句來為每條記錄載入詳細資訊(就是N
)。
- 你執行了一個單獨的 SQL 語句來獲取結果的一個列表(就是
- 這個問題會導致成百上千的 SQL 語句被執行。有時候,我們不希望產生這樣的後果。
關聯的巢狀結果對映【重點】
<association >
標籤中還可以直接巢狀結果對映,此時的Mybatis的查詢如下:
<!-- 定義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中對應的部門屬性名稱
javaType: 指定型別,可以是全類名或者別名
-->
<association property="department" javaType="com.xx.domain.Department">
<!--指定Department中的屬性對映,這裡也可以使用單獨拎出來,然後使用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>
總結
- 至此
有一個
型別的關聯已經完成了,學會一個<association>
使用即能完成。 - 注意: 關聯的巢狀 Select 查詢不建議使用,
N+1
是個重大問題,雖說Mybatis提供了延遲載入的功能,但是仍然不建議使用,企業開發中也是不常用的。
集合collection
- 集合,顧名思義,就是處理
有很多個
型別的關聯。 - 其中的屬性和
association
中的屬性類似,不再重複了。 - 比如這樣一個例子:查詢一個部門中的全部員工,查詢SQL如何寫呢?如下:
select * from department d left join user u on u.department_id=d.id;
- 此時的
User
實體類如下:
@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer deptId;
}
- 此時的
Department
實體類如下:
@Data
public class Department {
private Integer id;
private String name;
private List<User> users;
}
- 和
association
類似,同樣有兩種方式,我們可以使用巢狀 Select 查詢,或基於連線的巢狀結果對映集合。
集合的巢狀 Select 查詢【可以忽略】
- 不太重要,查詢如下:
<resultMap id="deptResult" type="com.xxx.domain.Department">
<!--指定Department中的屬性對映,這裡也可以使用單獨拎出來,然後使用association中的resultMap屬性指定-->
<id column="id" property="id"/>
<result column="name" property="name"/>
<!--
ofType:指定實際的JavaBean的全型別或者別名
select:指定巢狀的select查詢
javaType:集合的型別,可以不寫,Mybatis可以推測出來
-->
<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>
- 注意:這裡出現了一個不同於
association
的屬性ofType
,這個屬性非常重要,它用來將 JavaBean(或欄位)屬性的型別和集合儲存的型別區分開來。
集合的巢狀結果對映【重點】
- 現在你可能已經猜到了集合的巢狀結果對映是怎樣工作的——除了新增的
ofType
屬性,它和關聯的完全相同。 - 此時的Mybatis查詢如下:
<!--部門的resultMap-->
<resultMap id="deptResult" type="com.xxx.domain.Department">
<!--指定Department中的屬性對映,這裡也可以使用單獨拎出來,然後使用association中的resultMap屬性指定-->
<id column="dept_id" property="id"/>
<result column="dept_name" property="name"/>
<!--
ofType:指定實際的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>
總結
- 至此Mybatis第二彈之結果對映已經寫完了,如果覺得作者寫的不錯,給個在看關注一波,後續還有更多精彩內容推出。