6千字帶你看懂Mybatis字段映射-AS&ResultMap

前言

考慮到在Select時使用AS和方案一其實沒什麼差別,在介紹ResultMap之前,順便帶過一下。

方案二-Select … AS

當我們的數據庫列名和對象字段之間不是駝峯式命名的關係,我們可以在Select時使用AS,使得列名和對象名匹配上。
映射文件中是本次會執行的sql,我們會查出id,city_id,city_name,city_en_name。 按照開啓的駝峯式命名開關,我們會對應到對象的id,cityId,cityName,cityEnName字段。

<select id="selectCity" resultType="po.CityPO">
    select id,city_id,city_name,city_en_name from SU_City where id = #{id}
</select>

不過在這次,我們對PO做了小小的改動,把cityEnName改成了cityEnglishName。

public class CityPO {
	Integer id;
	Long cityId;
	String cityName;
	String cityEnglishName;   // 由cityEnName改成了cityEnglishName

由於找不到匹配的列,cityEnlishName肯定沒法被反射賦值,要爲Null了。

CityPO{id=2, cityId=2, cityName='北京', cityEnglishName='null'} 

**解決辦法: **在Select字段的時候使用AS,下面是改動後的映射文件。

<select id="selectCity" resultType="po.CityPO">
        select id,
        city_id,
        city_name,
        city_en_name AS cityEnglishName
        from SU_City
        where id = #{id}
</select>

改動後執行得到的結果如下。

CityPO{id=2, cityId=2, cityName='北京', cityEnglishName='beijing'} 

那麼我們來看看它是如何生效的,主要的代碼在哪裏。在昨天我們第一個介紹的函數handleRowValues中傳入了參數rsw,它是對ResultSet的一個包裝,在這個包裝裏,完成了具體使用哪個名字作爲數據庫的列名。

final ResultSetWrapper rsw = new ResultSetWrapper(rs, configuration);
handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);

在這個構造函數當中,我們會獲取數據庫的列名,AS爲什麼可以生效,具體就在下面這段代碼。

super();
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.resultSet = rs;
final ResultSetMetaData metaData = rs.getMetaData();
final int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
      // 在這裏
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      classNames.add(metaData.getColumnClassName(i));
}

在添加列名時,會從配置中獲取是否使用類標籤,isUseColumnLabel,默認爲true。根據Javadoc,這個ColumnLabel就是AS後的那個名字,如果沒有AS的話,就是獲取的原生的字段名。

    /**
     * Gets the designated column's suggested title for use in printouts and
     * displays. The suggested title is usually specified by the SQL <code>AS</code>
     * clause.  If a SQL <code>AS</code> is not specified, the value returned from
     * <code>getColumnLabel</code> will be the same as the value returned by the
     * <code>getColumnName</code> method.
     *
     * @param column the first column is 1, the second is 2, ...
     * @return the suggested column title
     * @exception SQLException if a database access error occurs
     */
    String getColumnLabel(int column) throws SQLException;

後面的過程就和昨天講的方案一一模一樣了,不再贅述。

方案三-ResultMap

resultMap 元素是 MyBatis 中最重要最強大的元素。它就是讓你遠離 90%的需要從結果 集中取出數據的 JDBC 代碼的那個東西, 而且在一些情形下允許你做一些 JDBC 不支持的事 情。 事實上, 編寫相似於對複雜語句聯合映射這些等同的代碼, 也許可以跨過上千行的代碼。 ResultMap 的設計就是簡單語句不需要明確的結果映射,而很多複雜語句確實需要描述它們 的關係。
ResultMap是Mybatis中可以完成複雜語句映射的東西,但在我們的日常開發中,我們往往是一個XML對應JavaBeans 或 POJOs(Plain Old Java Objects,普通 Java 對象),並沒有特別複雜的應用,下面也是基於日常的使用,看看簡單的ResultMap在源碼層面是如何展現的。

<resultMap id="cityMap" type="po.CityPO">
        <result column="id" property="id"/>
        <result column="city_id" property="cityId"/>
        <result column="city_name" property="cityName"/>
        <result column="city_en_name" property="cityEnglishName"/>
</resultMap>
<select id="selectCity" resultMap="cityMap">
        select id,
        city_id,
        city_name,
        city_en_name
        from SU_City
        where id = #{id}
</select>

在resultMap的子元素result對應了result和對象字段之間的映射,並通過id標示,你在Select語句中指定需要使用的resultMap即可。
源碼層面的話,依舊在DefaultResultSetHandler的handleResultSets中處理返回集合。

List<ResultMap> resultMaps = mappedStatement.getResultMaps(); 

在這次的ResultMap中,相比之前方案,其屬性更加的豐富起來。將之前寫的Result的信息保存在了resultMappings,idResultMappings等中,以備後續使用。

後續的函數走向和方案一二一致,在創建自動映射的時候出現了不同。

privateList<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix)throwsSQLException { 

在這個函數中,會獲取沒有映射過的列名。

final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix); 

之後會根據resultMap查看是否有未映射的字段。

loadMappedAndUnmappedColumnNames(resultMap, columnPrefix); 
List<String> mappedColumnNames = new ArrayList<String>();
    List<String> unmappedColumnNames = new ArrayList<String>();
    final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
    // 這裏沒有配置前綴,根據之前的圖,定義了ResultMap後,會記錄這些已經配置映射的字段。
    final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
    for (String columnName : columnNames) {
      // 遍歷列名,如果在已映射的配置中,那麼就加入已經映射的列名數據,
      final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
      if (mappedColumns.contains(upperColumnName)) {
        mappedColumnNames.add(upperColumnName);
      } else {
        unmappedColumnNames.add(columnName);
      }
    }
    // 生成未映射和已映射的Map
    mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
    unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);

如果有沒配置在ResultMap中,且Select出來的,那麼之後也會按照之前方案一那樣,繼續往下走,去對象中尋找映射關係。
由於沒有未映射的字段,使用自動映射的結果是false。

foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues; 

之後繼續往下走,使用applyPropertyMappings來創建對象。使用了PropertyMapping。裏面包含了字段名,列名,字段的類型和對應的處理器。

遍歷整個Mappings。

Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); 

函數裏主要的就是獲取這個字段對應的類型處理器,防止類型轉換失敗,這一部分下次會專門看一下。

final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);

TypeHandler就是一個接口,主要完成的工作就是從Result根據列名,獲取相應類型的值,爲下一步反射賦值做準備。至於它是怎麼決定爲什麼用這個類型的TypeHandler下次再看。最後就是給對應字段賦值。

metaObject.setValue(property, value); 

最後就完成了整個類的賦值。

總結

大致上,Mybatis完成映射主要是兩種方式。

  1. 只根據列名,利用自動映射,根據反射類的信息,得到列名和字段之間的關係,使用對應的TypeHandler,完成字段的賦值。

  2. 使用ResultMap預先定義好映射關係,也是最後根據TypeHandler和反射,完成字段的賦值。
    我個人感覺就簡單的用法來說,兩者都可以,在一次會話中,Configuration中的ResultMap關係建立好,在每一次查詢的時候就不用再去重新建立了,直接用就行。而自動映射的話,執行過一次後,也會在會話中建立自動映射的緩存。所以沒什麼差別。但如果複雜的映射的話,就非ResultMap莫屬啦。具體可以參考Mybatis文檔關於映射的章節,因爲目前用不到比較複雜的映射, 不做深究了。

http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章