Mybatis入門篇之結果映射,你射准了嗎?
目錄
- 前言
- 什麼是結果映射?
- 如何映射?
- 別名映射
- 駝峰映射
- 配置文件開啟駝峰映射
- 配置類中開啟駝峰映射
- 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第二彈之結果映射已經寫完了,如果覺得作者寫的不錯,給個在看關注一波,後續還有更多精彩內容推出。