MyBatis ResultType使用自定義TypeHandler

一、背景

最近在使用MyBatis自定義的TypeHandler在對數據作基礎加工,在對ResultMap使用自定義TypeHandler時是沒有問題的,能夠正常進入TypeHandler中處理數據,可是當結果集被定義爲ResultType時老是不進入自定義的TypeHandler,基於這個狀況,不得再也不次打開MyBatis的源碼一探究竟java

二、基礎代碼

/**
 * 自定義TypeHandler
 *
 * @author fulibao
 * @version 1.0
 * @created 2017/7/10 下午4:29
 **/
public class SecurityStringVarcharTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter);
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        //自定義代碼

        return rs.getString(columnName);
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        //自定義代碼

        return rs.getString(columnIndex);
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        //自定義代碼

        return cs.getString(columnIndex);
    }
}

MyBatis XML配置文件:數據庫

<select id="getByApplicationIds" parameterType="list" resultType="censorModel">
    <include refid="getAll"/>
    where application_id in
    <foreach collection="list" item="applicationId" index="index"
             open="(" close=")" separator=",">
        #{applicationId}
    </foreach>
</select>

mybatis-config.xml中對於typeHandler的配置緩存

<typeHandlers>
    <typeHandler jdbcType="VARCHAR" javaType="java.lang.String"
                 handler="com.meituan.fd.crm.common.typehandler.SecurityStringVarcharTypeHandler"/>
</typeHandlers>

三、問題排查

3.一、MyBatis執行查詢等操做的基礎類爲:DefaultSqlSession,其代碼爲:

public <T> T selectOne(String statement) {
  return this.<T>selectOne(statement, null);
}

public <T> T selectOne(String statement, Object parameter) {
  // Popular vote was to return null on 0 results and throw exception on too many.
  List<T> list = this.<T>selectList(statement, parameter);
  if (list.size() == 1) {
    return list.get(0);
  } else if (list.size() > 1) {
    throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
  } else {
    return null;
  }
}

public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
  return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
}

public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
  return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
}

public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
  final List<?> list = selectList(statement, parameter, rowBounds);
  final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
      configuration.getObjectFactory(), configuration.getObjectWrapperFactory());
  final DefaultResultContext context = new DefaultResultContext();
  for (Object o : list) {
    context.nextResultObject(o);
    mapResultHandler.handleResult(context);
  }
  Map<K, V> selectedMap = mapResultHandler.getMappedResults();
  return selectedMap;
}

public <E> List<E> selectList(String statement) {
  return this.selectList(statement, null);
}

public <E> List<E> selectList(String statement, Object parameter) {
  return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    return result;
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

能夠看到不管是selectOne、selectMap、selectList最終調用的都是selectList方法,所以咱們由selectList入手開始排查問題。mybatis

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    //獲取本次執行SQL的相關配置信息
    //MappedStatement中包含了一次執行的因此配置信息,包括SQL、配置參數等等
    MappedStatement ms = configuration.getMappedStatement(statement);
    //由executor調度執行查詢,executor主要有兩類:BaseExecutor(基礎)、CachingExecutor(執行二級緩存)
    //現有系統這邊沒有開啓二級緩存,所以我這邊進入BaseExecutor
    List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    return result;
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

BaseExecutor.query方法:app

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   //獲取執行的BoundSql(詳細內容能夠參見另一篇文章:經過BoundSql獲取所有執行SQL--MyBatis源碼分析)
   BoundSql boundSql = ms.getBoundSql(parameter);
   //建立緩存key
   CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
   //執行真正的查詢,見下面方法
   return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) throw new ExecutorException("Executor was closed.」);
  //緩存相關,查看是否須要清理緩存
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;
    //resultHandler不爲空的狀況下由緩存獲取,此處resultHandler爲空
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      //若是由緩存中取出了數據,那麼處理存儲過程相關的輸出參數
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      //由數據庫獲取數據及處理數據,方法見下面
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    deferredLoads.clear(); // issue #601
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      clearLocalCache(); // issue #482
    }
  }
  return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    //真正的執行查詢的方法,這個方法在BaseExecutor中是一個抽象方法,交由子類來完成,這裏咱們進入子類:SimpleExecutor中查看
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    //獲取MyBatis總的配置信息
    Configuration configuration = ms.getConfiguration();
    //建立StatementHandler,它用於執行SQL及處理執行結果
    //它有四個實現類:見下圖分析:
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    //將參數等信息注入到執行SQL中
    stmt = prepareStatement(handler, ms.getStatementLog());
    //執行查詢,通常的SQL執行都是PreparedStatementHandler,進入它的query中
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  //JDBC級別的SQL執行
  ps.execute();
  //resultSetHandler只有一個實現類:DefaultResultSetHandler,進入看一下:
  return resultSetHandler.<E> handleResultSets(ps);
}
public List<Object> handleResultSets(Statement stmt) throws SQLException {
  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
  //最終執行完成後獲取的結果數據
  final List<Object> multipleResults = new ArrayList<Object>();

  int resultSetCount = 0;
  //ResultSet包裝類,內部包含了ResultSet,及其元數據
  ResultSetWrapper rsw = getFirstResultSet(stmt);

  //獲取結果集配置
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  int resultMapCount = resultMaps.size();
  validateResultMapsCount(rsw, resultMapCount);
  while (rsw != null && resultMapCount > resultSetCount) {
    ResultMap resultMap = resultMaps.get(resultSetCount);
    //處理結果集,MyBatis的查詢結果就出自這裏,進入看一下。。
    handleResultSet(rsw, resultMap, multipleResults, null);
    rsw = getNextResultSet(stmt);
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }

  String[] resultSets = mappedStatement.getResulSets();
  if (resultSets != null) {
    while (rsw != null && resultSetCount < resultSets.length) {
      ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
      if (parentMapping != null) {
        String nestedResultMapId = parentMapping.getNestedResultMapId();
        ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
        handleResultSet(rsw, resultMap, null, parentMapping);
      }
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
  }

  return collapseSingleResultList(multipleResults);
}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  try {
    if (parentMapping != null) {
      handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
    } else {
      if (resultHandler == null) {
        //默認的結果集處理Handler
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
        //處理沒一行數據,並將數據存入defaultResultHandler
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
        //將數據存入multipleResults
        multipleResults.add(defaultResultHandler.getResultList());
      } else {
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
      }
    }
  } finally {
    closeResultSet(rsw.getResultSet()); // issue #228 (close resultsets)
  }
}
private void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  if (resultMap.hasNestedResultMaps()) {
    ensureNoRowBounds();
    checkResultHandler();
    handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  } else {
    //這裏是最終處理數據的位置:
    handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  }
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
  //默認結果集存放處,記錄了當前最新的一個結果和記錄數
  DefaultResultContext resultContext = new DefaultResultContext();
  //根據RowBounds跳過不須要處理的數據
  skipRows(rsw.getResultSet(), rowBounds);
  //逐行處理數據
  while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
    ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
    //處理一行數據,方法見下面
    Object rowValue = getRowValue(rsw, discriminatedResultMap);
    //存儲處理後的一行數據
    storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
  }
}

//ResultType和ResultMap的差別就出如今這裏ide

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  //獲取查詢實體的一個實例
  Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);
  if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
    //MyBatis強大的MetaObject
    final MetaObject metaObject = configuration.newMetaObject(resultObject);
    boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
    if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {  
      //對於未被配置進入的列進行處理(ResultType所對應的列,一概不會被MyBatis配置,都會在這個方法中處理),方法見下面:      
      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
    }
    //對於配置進入的列進行處理(ResultMap中的列都會被配置,會在此到處理),方法見下面
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    resultObject = foundValues ? resultObject : null;
    return resultObject;
  }
  return resultObject;
}
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
  boolean foundValues = false;
  //對每一列進行處理
  for (String columnName : unmappedColumnNames) {
    //獲取列名
    String propertyName = columnName;
    if (columnPrefix != null && columnPrefix.length() > 0) {
      // When columnPrefix is specified,
      // ignore columns without the prefix.
      if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
        propertyName = columnName.substring(columnPrefix.length());
      } else {
        continue;
      }
    }
    //獲取對應於實體的屬性
    final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
    if (property != null && metaObject.hasSetter(property)) {
      //獲取當前列對應的JavaType
      final Class<?> propertyType = metaObject.getSetterType(property);
      if (typeHandlerRegistry.hasTypeHandler(propertyType)) {
        //獲取typeHandler,終於找到了問題的根節點了,在這兒就能夠看出,ResultType中的列在MyBatis中也是使用TypeHandler處理的,
        //未進入自定義的TypeHandler的緣由只能是MyBatis的查詢TypeHandler的方法並無查詢到咱們自定義的TypeHandler,咱們看一下這個查詢方法,見下面
        final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
        //由TypeHandler獲取當前列執行結果
        final Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
        if (value != null || configuration.isCallSettersOnNulls()) { // issue #377, call setter on nulls
          if (value != null || !propertyType.isPrimitive()) {
            //由metaObject將列值設置進入屬性值
            metaObject.setValue(property, value);
          }
          foundValues = true;
        }
      }
    }
  }
  return foundValues;
}

rsw.getTypeHandler(propertyType, columnName); 方法:源碼分析

public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
  TypeHandler<?> handler = null;
  //獲取當前列是否已查詢過TypeHandler
  Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);
  if (columnHandlers == null) {
    //沒有則新建一個當前列對應的TypeHandler Map
    columnHandlers = new HashMap<Class<?>, TypeHandler<?>>();
    typeHandlerMap.put(columnName, columnHandlers);
  } else {
    //存在,則直接取出TypeHandler
    handler = columnHandlers.get(propertyType);
  }
  if (handler == null) {
    //由MyBatis配置文件中取出當前JavaType對應的TypeHandler
    //看到問題了吧,查詢時,只傳入了JavaType,並無傳JdbcType,是否意味着,會查詢出JavaType爲當前列的JavaType,JdbcType爲null的TypeHandler?進入方法看一下就知道了
    handler = typeHandlerRegistry.getTypeHandler(propertyType);
    // Replicate logic of UnknownTypeHandler#resolveTypeHandler
    // See issue #59 comment 10
    if (handler == null || handler instanceof UnknownTypeHandler) {
      final int index = columnNames.indexOf(columnName);
      final JdbcType jdbcType = jdbcTypes.get(index);
      final Class<?> javaType = resolveClass(classNames.get(index));
      if (javaType != null && jdbcType != null) {
        handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
      } else if (javaType != null) {
        handler = typeHandlerRegistry.getTypeHandler(javaType);
      } else if (jdbcType != null) {
        handler = typeHandlerRegistry.getTypeHandler(jdbcType);
      }
    }
    if (handler == null || handler instanceof UnknownTypeHandler) {
      handler = new ObjectTypeHandler();
    }
    columnHandlers.put(propertyType, handler);
  }
  return handler;
}

typeHandlerRegistry.getTypeHandler(propertyType); 方法:ui

public <T> TypeHandler<T> getTypeHandler(Class<T> type) {
  return getTypeHandler((Type) type, null);
}
繼續進入:
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
  //獲取當前列對應的JavaType全部已配置的TypeHandler
  Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
  TypeHandler<?> handler = null;
  if (jdbcHandlerMap != null) {
    //獲取jdbcType對應的TypeHandler
    //當前jdbcType爲Null,取出的果真是:JavaType爲當前列的JavaType,JdbcType爲null的TypeHandler
    //問題揭開了,仍是咱們配置爲有問題,咱們自定義的TypeHandler配置的JavaType爲:java.lang.String,JdbcType爲VARCHAR,因此確定不會使用咱們自定義的TypeHandler
    handler = jdbcHandlerMap.get(jdbcType);
    if (handler == null) {
      handler = jdbcHandlerMap.get(null);
    }
  }
  if (handler == null && type != null && type instanceof Class && Enum.class.isAssignableFrom((Class<?>) type)) {
    handler = new EnumTypeHandler((Class<?>) type);
  }
  @SuppressWarnings("unchecked")
  // type drives generics here
  TypeHandler<T> returned = (TypeHandler<T>) handler;
  return returned;
}
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
    throws SQLException {
  final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  boolean foundValues = false;
  //獲取全部配置的ResultMapping
  final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  //根據每一個ResultMapping來處理每一列的值,ResultMapping中包含了當前列的TypeHandler
  for (ResultMapping propertyMapping : propertyMappings) {
    final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    if (propertyMapping.isCompositeResult() 
        || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) 
        || propertyMapping.getResultSet() != null) {
      //處理當前列的值,此方法見下面:
      Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
      final String property = propertyMapping.getProperty(); // issue #541 make property optional
      if (value != NO_VALUE && property != null && (value != null || configuration.isCallSettersOnNulls())) { // issue #377, call setter on nulls
        if (value != null || !metaObject.getSetterType(property).isPrimitive()) {
          metaObject.setValue(property, value);
        }
        foundValues = true;
      }
    }
  }
  return foundValues;
}

getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);方法:this

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
    throws SQLException {
  if (propertyMapping.getNestedQueryId() != null) {
    return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
  } else if (propertyMapping.getResultSet() != null) {
    addPendingChildRelation(rs, metaResultObject, propertyMapping);
    return NO_VALUE;
  } else if (propertyMapping.getNestedResultMapId() != null) {
    // the user added a column attribute to a nested result map, ignore it
    return NO_VALUE;
  } else {
    //由TypeHandler獲取數據
    final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
    final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    return typeHandler.getResult(rs, column);
  }
}

StatementHandler類:code

  • 基礎實現類:BaseStatementHandler,它有三個實現類,分別對應CallableStatementHandler(存儲過程)、PreparedStatementHandler(對應於JDBC中prepareStatement執行的SQL)、SimpleStatementHandler(對應於JDBC中Statement執行的SQL)

  • 路由StatementHandler:RoutingStatementHandler,它無業務性,只是提供了一種路由,來產生相應的BaseStatementHandler

四、解決方法

問題排查完畢,是配置自定義TypeHandler時指定了JaveType和JdbcType致使MyBatis找不到自定義的TypeHandler,所以使用以下配置方式便可解決問題:

<typeHandlers>
    <typeHandler javaType="java.lang.String"
                 handler="com.meituan.fd.crm.common.typehandler.SecurityStringVarcharTypeHandler"/>
    <typeHandler jdbcType="VARCHAR" javaType="java.lang.String"
                 handler="com.meituan.fd.crm.common.typehandler.SecurityStringVarcharTypeHandler"/>
</typeHandlers>

此時,javaType爲String,JdbcType爲VARCHAR和null的都被配置爲自定義的TypeHandler

相關文章
相關標籤/搜索