TypeHandler注册、获取和参数解析

1,944 阅读5分钟

TypeHandler

TypeHandler的主要工作是完成对Java代码和数据库中类型的映射,完成PrepareStatement参数的填充(JavaType->JDBCType)和ResultSet的解析(JDBCType->JavaType)。

public interface TypeHandler<T> {

  //设置参数
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  //取得结果,供普通select用
  T getResult(ResultSet rs, String columnName) throws SQLException;

  //取得结果,供普通select用
  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  //取得结果,供存储过程用(stored procedure)
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

 Mybatis自身提供了基础Java对象的TypeHandler。

BaseTypeHandler-自定义TypeHandler

主要通过对org.apache.ibatis.type.BaseTypeHandler的继承,可以实现对Java字段类型和JDBC类型的设置和转换。BaseTypeHandler对TypeHandler进行了封装,增加了入参为空或返回值为空的预处理。

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  protected Configuration configuration;

  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }

  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      if (jdbcType == null) { // 检查jdbcType
        throw new TypeException("方便阅读,已删减");
      }
      try {
        ps.setNull(i, jdbcType.TYPE_CODE); // 检查jdbcType
      } catch (SQLException e) {
        throw new TypeException("方便阅读,已删减 " + e, e);
      }
    } else {
      // 
      setNonNullParameter(ps, i, parameter, jdbcType);
    }
  }

  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    T result = getNullableResult(rs, columnName);
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    T result = getNullableResult(rs, columnIndex);
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    T result = getNullableResult(cs, columnIndex);
    if (cs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

	// 对JDBC PreparedStatement中的参数设定交给子类完成
  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

	// 对返回值的处理交给子类完成
  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

}

以BigDecimalTypeHandler为例

public class BigDecimalTypeHandler extends BaseTypeHandler<BigDecimal> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, BigDecimal parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setBigDecimal(i, parameter);
  }

  @Override
  public BigDecimal getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getBigDecimal(columnName);
  }

  @Override
  public BigDecimal getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getBigDecimal(columnIndex);
  }

  @Override
  public BigDecimal getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getBigDecimal(columnIndex);
  }
}

BaseTypeHandler和TypeHandler的继承关系

TypeHandler的注册

Mybatis中所有自定义或内置基础的TypeHandler,都保存在org.apache.ibatis.type.TypeHandlerRegistry中。

初始化TypeHandlerRegistry对象时,完成基础类型的注册

TypeHandlerRegistry中,包含3个Map和一个 UNKNOWN_TYPE_HANDLER:

  • JDBC_TYPE_HANDLER_MAP:Map<JdbcType, TypeHandler<?>>  支持按JDBC查找TypeHandler
  • TYPE_HANDLER_MAP:Map<Type, Map<JdbcType, TypeHandler<?>>> 支持按JavaType查找TypeHandler
  • ALL_TYPE_HANDLERS_MAP:Map<Class, TypeHandler> 支持按TypeHandler.class查找TypeHandler
  • UNKNOWN_TYPE_HANDLER:当查询不到对应的TypeHandler时作为默认TypeHandler;设置参数时执行逻辑按ObjectTypeHandler中的参数设定逻辑执行

TypeHandlerRegistry并提供了多种register方法,以config解析XMLConfigBuilder类中的typeHandlerElement方法为例:

一段TypeHandler配置

XMLConfigBuilder类中的typeHandlerElement方法

private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // package 解析
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          //调用TypeHandlerRegistry.register,去包下找所有类
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          // 单个 typeHandler
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          // 调用TypeHandlerRegistry.register
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              // 只指定javaType 
              // jdbcType 从@MappedJdbcTypes解析
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              // 同时指定javaType jdbcType 
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            // 按 handler类名,
            // javaType 从@MappedTypes 或 向上遍历TypeReference取泛型参数的类型
            // jdbcType 从@MappedJdbcTypes
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

TypeHandler的获取

getTypeHandler的方法

public TypeHandler<?> getTypeHandler(JdbcType jdbcType) {
  return JDBC_TYPE_HANDLER_MAP.get(jdbcType);
}

private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    // 按JavaType查询JdbcType Map
    Map<JdbcTypeTypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
      // 在JdbcType Map获取最终的TypeHandler
      handler = jdbcHandlerMap.get(jdbcType);
      if (handler == null) {
        // 拿默认Handler
        handler = jdbcHandlerMap.get(null);
      }
    }
    if (handler == null && type != null && type instanceof Class && Enum.class.isAssignableFrom((Class<?>) type)) {
      handler = new EnumTypeHandler((Class<?>) type);
    }
    // type drives generics here
    return (TypeHandler<T>) handler;
  }

参数与Handler绑定

Mapper解析时,Mybatis将语句中的不同数据类型的JdbcType、JavaType和解析出的TypeHandler一同组成ParameterMapping对象;对象绑定到对应的SqlSource中,保存到全局Configuration中的MappedStatement中。

本部分列出JavaType和JdbcType解析位置的核心代码,如果对Mapper和Config解析全流程感兴趣的同学可以从下边的入口进入。

(入口:XMLConfigBuilder->XMLMapperBuilder->XMLStatementBuilder->XMLLanguageDriver->RawSQLSource/DynamicSqlSource->SQLSourceBuilder->ParameterMappingTokenHandler)

ParameterExpression对象的结构

解析动态入参的类型和TypeHandler

private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

    @Override
    public String handleToken(String content) { 
      parameterMappings.add(buildParameterMapping(content));
      // jdbc Preparestatement参数占位 ?, ?, ?
      return "?";
    }

    private ParameterMapping buildParameterMapping(String content) {
      //content = "name, JavaType=String, JdbcType=VARCHAR"
      //先解析参数映射,就是转化成一个hashmap
      // 代码简化应该是通过方法拿到 ParameterExpression(继承HashMap)对象;
      Map<StringString> propertiesMap = new ParameterExpression(content)
      // 省略部分代码....
      ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
      Class<?> javaType = propertyType;
      String typeHandlerAlias = null;
      for (Map.Entry<StringString> entry : propertiesMap.entrySet()) {
        String name = entry.getKey();
        String value = entry.getValue();
        if ("javaType".equals(name)) {
          javaType = resolveClass(value);  // 解析JavaType
          builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {  // 解析JdbcType
          builder.jdbcType(resolveJdbcType(value));
        } else if ("mode".equals(name)) {
          builder.mode(resolveParameterMode(value));
        } else if ("numericScale".equals(name)) {
          builder.numericScale(Integer.valueOf(value));
        } else if ("resultMap".equals(name)) {
          builder.resultMapId(value);
        } else if ("typeHandler".equals(name)) {
          typeHandlerAlias = value;  // 解析typeHandler
        } else if ("jdbcTypeName".equals(name)) {
          builder.jdbcTypeName(value);
        } else if ("property".equals(name)) {
          // Do Nothing
        } else if ("expression".equals(name)) {
          throw new BuilderException("Expression based parameters are not supported yet");
        } else {
          throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + parameterProperties);
        }
      }
      // 如果 #{} 参数中指定了typeHandler直接用
      // name, JavaType=String, JdbcType=VARCHAR, typeHandler=FastjsonListTypeHandler
      if (typeHandlerAlias != null) {   
        builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
      }

      return builder.build();
    }
  }

ParameterMapping的build方法和Handler对象的选定

public class ParameterMapping {
  
    private String property;
   
    private ParameterMode mode;
    // javaType
    private Class<?> javaType = Object.class;
    // jdbcType
    private JdbcType jdbcType;
    private Integer numericScale;
    // handler Mapper解析时已经完成handler的选定
    private TypeHandler<?> typeHandler;
    private String resultMapId;
    private String jdbcTypeName;
    private String expression;
    
    public ParameterMapping build() {
      resolveTypeHandler();
      validate();
      return parameterMapping;
    }
  
  	// 至此 完成参数填充阶段的 ParameterMapping 的构建
    private void resolveTypeHandler() {
      if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) {
        Configuration configuration = parameterMapping.configuration;
 				// 1.从全局configuration中拿到TypeHandlerRegistry
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
       	// 2.拿TypeHandler
        parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType);
      }
    }
}

使用TypeHandler设置参数

执行查询时,会从MappedStatement中获取SourceSql,并以SourceSql构建BoundSql对象。

// Executor中的query方法
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //得到绑定sql
    BoundSql boundSql = ms.getBoundSql(parameter);
    //创建缓存Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    //查询
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

public BoundSql getBoundSql(Object parameterObject) {
	  //其实就是调用sqlSource.getBoundSql
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

public class BoundSql {
  private String sql;
  private List<ParameterMapping> parameterMappings;
  private Object parameterObject;
  private Map<String, Object> additionalParameters;
  private MetaObject metaParameters;
}

利用BoundSql对象中的信息,在执行query前对入参做替换。

public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
    throws SQLException {
  Statement stmt = null;
  try {
    flushStatements();
    Configuration configuration = ms.getConfiguration();
    // 创建语句的Handler,
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
    Connection connection = getConnection(ms.getStatementLog());
    stmt = handler.prepare(connection);
    // 处理参数
    handler.parameterize(stmt);
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
public void setParameters(PreparedStatement ps) throws SQLException {
  ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings != null) {
    //循环设参数
    for (int i = 0; i < parameterMappings.size(); i++) {
      ParameterMapping parameterMapping = parameterMappings.get(i);
      if (parameterMapping.getMode() != ParameterMode.OUT) { // IN 入参 OUT出参,resultMap
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        JdbcType jdbcType = parameterMapping.getJdbcType();
        if (value == null && jdbcType == null) {
          jdbcType = configuration.getJdbcTypeForNull();
        }
        // 调用TypeHandler的setParameter
        typeHandler.setParameter(ps, i + 1, value, jdbcType);
      }
    }
  }
}

至此,TypeHandler注册、获取和在参数设定的部分完成。

参考文献

PreparedStatement 预编译SQL:docs.oracle.com/javase/8/do…

Mybatis TypeHandler:mybatis.org/mybatis-3/c…