Mybatis之對象工廠

前言

在上文Mybatis之XML如何映射到方法中講到結果映射的時候,須要建立好對象,而後再給對象的屬性賦值,而建立對象就用到了Mybatis的內置的對象工廠類DefaultObjectFactory,固然Mybatis也提供了擴展機制,用戶能夠實現本身的對象工廠。java

對象工廠

上文中介紹告終果映射的相關邏輯在DefaultResultSetHandler處理器中,下面重點看一下建立結果對象的方法:git

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
      throws SQLException {
    final Class<?> resultType = resultMap.getType();
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
    } else if (!constructorMappings.isEmpty()) {
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
      return objectFactory.create(resultType);
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
      return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
    }
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
  }

這是建立結果對象的核心方法,建立對象的時候分紅了4中狀況分別是:
1.結果對象Mybatis自己提供了處理器,也就是xxTypeHandler,更多實如今org.apache.ibatis.type路徑下,這種狀況能夠直接從Resultset中獲取結果返回結果值,能夠認爲都是原生的類型;
2.在結果映射中指定了構造器,無需使用默認的構造器;
3.結果對象是接口或者結果對象有默認的構造器;
4.以上狀況都不知足會檢查是否配置了自動映射,默認開啓;
以上狀況都不知足則直接拋出異常,下面具體分析一下4種類型都在什麼狀況下執行;github

1.原生類型

表示結果集是原生類型,好比string類型,相關xml配置例如:apache

<select id="selectBlog" parameterType="hashmap" resultType="string">
        select title from blog where id = #{id} and
        author=#{author,javaType=string}
    </select>

直接返回了原生類型string,Mybatis提供了StringTypeHandler處理器:segmentfault

private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final Class<?> resultType = resultMap.getType();
    final String columnName;
    if (!resultMap.getResultMappings().isEmpty()) {
      final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
      final ResultMapping mapping = resultMappingList.get(0);
      columnName = prependPrefix(mapping.getColumn(), columnPrefix);
    } else {
      columnName = rsw.getColumnNames().get(0);
    }
    final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
    return typeHandler.getResult(rsw.getResultSet(), columnName);
  }

首先獲取字段名稱,而後經過返回類型獲取處理器,最後直接從ResultSet中獲取結果;在獲取字段名稱的時候也分兩種狀況分別是直接配置resultType和配置resultMap兩種狀況,無論哪一種狀況若是配置了多個映射字段都只獲取第一個,好比:mybatis

select id,title from blog where id = #{id} and author=#{author,javaType=string}

這種狀況只會返回id的值,而且被轉爲string類型;app

2.指定構造器

在resultmap中指定了構造器參數,好比以下的例子:ide

<resultMap id="blogResultMap" type="blog">
        <constructor>
            <idArg column="id" javaType="long"/>
        </constructor>
        <result property="title" column="title" />
    </resultMap>

如上所示指定了id爲參數的構造器,這種狀況在經過對象工廠建立對象的時候不會使用默認的無參構造器,會使用帶id參數的構造器,部分代碼以下所示:ui

objectFactory.create(resultType, constructorArgTypes, constructorArgs)

三個參數分別是:返回的對象類型,構造器參數類型,構造器參數值;code

3.默認構造器

無需指定構造器參數類型和構造器參數值,固然前提是類提供了默認的構造器,直接調用create方法:

objectFactory.create(resultType)

同指定構造器的方式惟一的區別就是構造器參數類型和構造器參數值都爲null;下面具體看一下對象工廠的默認實現:

@Override
  public <T> T create(Class<T> type) {
    return create(type, null, null);
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    Class<?> classToCreate = resolveInterface(type);
    // we know types are assignable
    return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
  }
  
private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    try {
      Constructor<T> constructor;
      if (constructorArgTypes == null || constructorArgs == null) {
        constructor = type.getDeclaredConstructor();
        if (!constructor.isAccessible()) {
          constructor.setAccessible(true);
        }
        return constructor.newInstance();
      }
      constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
      if (!constructor.isAccessible()) {
        constructor.setAccessible(true);
      }
      return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
    } catch (Exception e) {
      ...省略...
    }
  }

分別提供了有參數和無參數的構造器,沒有參數的時候直接調用默認的構造器;有指定構造器參數類型,則獲取有參數類型的構造器,固然對應的類裏面須要提供對應參數的構造器,否則會報錯;獲取構造器以後,直接經過newInstance建立類對象;

4.自動映射

即沒有提供默認的構造器也沒有提供指定構造參數的構造器,那麼Mybatis會進行自動映射;自動映射是有一個開關的,默認開啓,能夠在configuration裏面和resultMap裏面配置autoMapping開關;準備以下實例看具體是如何映射的:

<resultMap id="blogResultMap" type="blog" autoMapping="true">
        <result property="id" column="id" />
        <result property="title" column="title" />
        <result property="content" column="content"/>
    </resultMap>
    <select id="selectBlogMap" parameterType="hashmap" resultMap="blogResultMap">
        select id,title from blog where id = #{id} and
        author=#{author,javaType=string}
    </select>

blog類裏面提供了id爲參數的構造器,這樣就沒有了默認的構造器;這時候Mybatis會找blog裏面是否存在id,title類參數的構造器,若是存在則獲取此構造器建立對象,不存在則報錯,以下所示:

Caused by: org.apache.ibatis.executor.ExecutorException: No constructor found in com.mybatis.vo.Blog matching [java.lang.Long, java.lang.String]

5.沒法建立對象

若是第四種狀況也不知足,便可以配置autoMapping="false",則Mybatis直接拋出沒法建立對象,具體異常以下所示:

Caused by: org.apache.ibatis.executor.ExecutorException: Do not know how to create an instance of class com.mybatis.vo.Blog

自定義對象工廠

實現本身的對象工廠也很簡單,實現接口ObjectFactory或者重載DefaultObjectFactory便可,此處爲了方便咱們直接重載DefaultObjectFactory,並實現當須要實例化的對象是Blog時,若是沒有指定author則給定一個默認值,實現以下:

public class MyObjectFactory extends DefaultObjectFactory {

    private static final long serialVersionUID = 1L;

    @Override
    public <T> T create(Class<T> type) {
        System.out.println("create:" + type);
         if (type.equals(Blog.class)){
             Blog blog = (Blog)super.create(type);
             blog.setAuthor("ksfzhaohui");
             return (T) blog;
         }
        return super.create(type);
    }
    
    ...省略...
}

實現也很簡單,只須要在create方法中斷定指定的類型爲Blog,而後調用父類的create方法建立對象,而後設置默認值;固然還須要在configuration中配置自定義對象工廠才能生效:

<objectFactory type="com.mybatis.myObjectFactory.MyObjectFactory"> 
            <property name="name" value="MyObjectFactory"/> 
    </objectFactory>

總結

本文重點介紹了默認的對象工廠和建立對象的五種狀況分別是:原生的類型,指定了構造器,有默認構造器的類,使用自動映射的狀況以及不知足前面四種狀況的處理;最後簡單試下了一個自定義的對象工廠,用來給指定的類對象指定默認的屬性值。

示例代碼地址

Github

相關文章
相關標籤/搜索