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

目錄

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