天天看點

從零開始SpringCloud Alibaba實戰(61)——Mybatis之結果映射

文章目錄

    • 前言
    • 别名映射
    • 駝峰映射
    • resultMap映射
    • 進階結果映射
      • 關聯(association)
      • 例子
    • 關聯的嵌套結果映射

前言

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>


           

配置檔案開啟駝峰映射

隻需要在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即可。

的屬性如下:

id:唯一辨別這個resultMap,同一個Mapper.xml中不能重複

type:指定JavaBean的類型,可以是全類名,也可以是别名

子标簽的屬性如下:

column:SQL傳回的字段名稱

property:JavaBean中屬性的名稱

javaType:一個 Java 類的全限定名,或一個類型别名(關于内置的類型别名,可以參考上面的表格)。 如果你映射到一個 JavaBean,MyBatis 通常可以推斷類型。然而,如果你映射到的是 HashMap,那麼你應該明确地指定 javaType 來保證行為與期望的相一緻。

jdbcType:JDBC 類型,所支援的 JDBC 類型參見這個表格之後的“支援的 JDBC 類型”。 隻需要在可能執行插入、更新和删除的且允許空值的列上指定 JDBC 類型。這是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 程式設計,你需要對可以為空值的列指定這個類型。

typeHandler: 這個屬性值是一個類型處理器實作類的全限定名,或者是類型别名。

resultMap:結果映射的 ID,可以将此關聯的嵌套結果集映射到一個合适的對象樹中。 它可以作為使用額外 select 語句的替代方案。

進階結果映射

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;
}


           

關聯的嵌套 Select 查詢

<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 語句被執行。有時候,我們不希望産生這樣的後果。

關聯的嵌套結果映射

标簽中還可以直接嵌套結果映射,此時的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,
department d where u.dept_id=d.id
</select>