添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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…

  • 手把手教你注册和使用ChatGPT
  • OpenAI 推出超神 ChatGPT 注册攻略来了
  • 私信