Mybatis入門篇之結果對映,你射準了嗎?

愛撒謊的男孩發表於2020-09-04

目錄

  • 前言
  • 什麼是結果對映?
  • 如何對映?
    • 別名對映
    • 駝峰對映
      • 配置檔案開啟駝峰對映
      • 配置類中開啟駝峰對映
    • resultMap對映
    • 總結
  • 高階結果對映
    • 關聯(association)
      • 例子
      • 關聯的巢狀 Select 查詢
      • 關聯的巢狀結果對映
      • 總結
    • 集合collection
      • 集合的巢狀 Select 查詢
      • 集合的巢狀結果對映
  • 總結

前言

  • 上一篇文章介紹了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 對映語句來載入期望的複雜型別。
    • 巢狀結果對映:使用巢狀的結果對映來處理連線結果的重複子集。
  • 首先,先讓我們來看看這個元素的屬性。你將會發現,和普通的結果對映相比,它只在 selectresultMap 屬性上有所不同。
    • property: 對映到列結果的欄位或屬性。如果用來匹配的 JavaBean 存在給定名字的屬性,那麼它將會被使用。
    • javaType:一個 Java 類的完全限定名,或一個型別別名(關於內建的型別別名,可以參考上面的表格)
      jdbcType: JDBC 型別, 只需要在可能執行插入、更新和刪除的且允許空值的列上指定 JDBC 型別
    • typeHandler:使用這個屬性,你可以覆蓋預設的型別處理器。 這個屬性值是一個型別處理器實現類的完全限定名,或者是型別別名。
      column: 資料庫中的列名,或者是列的別名。一般情況下,這和傳遞給 resultSet.getString(columnName) 方法的引數一樣。 注意:在使用複合主鍵的時候,你可以使用 column="{prop1=col1,prop2=col2}" 這樣的語法來指定多個傳遞給巢狀 Select 查詢語句的列名。這會使得prop1prop2 作為引數物件,被設定為對應巢狀 Select 語句的引數。
    • select:用於載入複雜型別屬性的對映語句的 ID,它會從 column 屬性指定的列中檢索資料,作為引數傳遞給目標 select 語句。 具體請參考下面的例子。注意:在使用複合主鍵的時候,你可以使用column="{prop1=col1,prop2=col2}" 這樣的語法來指定多個傳遞給巢狀 Select 查詢語句的列名。這會使得 prop1 和 prop2 作為引數物件,被設定為對應巢狀 Select 語句的引數。
    • fetchType:可選的。有效值為 lazyeager。 指定屬性後,將在對映中忽略全域性配置引數 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 語句被執行。有時候,我們不希望產生這樣的後果。

關聯的巢狀結果對映【重點】

  • <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第二彈之結果對映已經寫完了,如果覺得作者寫的不錯,給個在看關注一波,後續還有更多精彩內容推出。

相關文章