Mybatis注册TypeHandler以及TypeHandler的源码流程

在将如何注册TypeHandler之前,我们先通过Mybatis的源码来看看TypeHandler的生成流程。

SqlSessionFactory在实例化时会立即解析Mapper.xml内的xml元素,将其从xml节点解析为相应的Java类,我们先从resultMap节点的解析上看TypeHandler的生成流程。

public class XMLMapperBuilder extends BaseBuilder {

    private ResultMapping buildResultMappingFromContext(XNode context, Class resultType, List flags) throws Exception {
        String property;
        if (flags.contains(ResultFlag.CONSTRUCTOR)) {
          property = context.getStringAttribute("name");
        } else {
          property = context.getStringAttribute("property");
        }
        String column = context.getStringAttribute("column");
        String javaType = context.getStringAttribute("javaType");
        String jdbcType = context.getStringAttribute("jdbcType");
        String nestedSelect = context.getStringAttribute("select");
        String nestedResultMap = context.getStringAttribute("resultMap",
            processNestedResultMappings(context, Collections. emptyList()));
        String notNullColumn = context.getStringAttribute("notNullColumn");
        String columnPrefix = context.getStringAttribute("columnPrefix");

        // 提取result元素上的typeHandler信息,如果未定义,则默认null
        String typeHandler = context.getStringAttribute("typeHandler");
        String resultSet = context.getStringAttribute("resultSet");
        String foreignColumn = context.getStringAttribute("foreignColumn");
        boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
        Class javaTypeClass = resolveClass(javaType);

        //解析TypeHandler,获取其Class对象
        Class> typeHandlerClass = (Class>) resolveClass(typeHandler);

        //解析jdbc类型
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        // 构建ResuleMapping对象
        return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }

    protected Class resolveClass(String alias) {
        if (alias == null) {
          return null;
        }
        try {
          return resolveAlias(alias);  // 如果typeHandler定义了,那么进行解析
        } catch (Exception e) {
          throw new BuilderException("Error resolving class. Cause: " + e, e);
        }
    }

    protected Class resolveAlias(String alias) {
        return typeAliasRegistry.resolveAlias(alias);
    }

}

public class TypeAliasRegistry {

    public  Class resolveAlias(String string) {
        try {
          if (string == null) {
            return null;
          }
          // issue #748
          String key = string.toLowerCase(Locale.ENGLISH);
          Class value;
          if (TYPE_ALIASES.containsKey(key)) {
            value = (Class) TYPE_ALIASES.get(key);
          } else {
            value = (Class) Resources.classForName(string);  // 使用Class.forName形式得到指定字符串代表Class对象
          }
          return value;
        } catch (ClassNotFoundException e) {
          throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
        }
  }

}

通过以上代码可知,当result元素定义了typeHandler属性时,只要其值是一个类的全名,那么可以通过Class.forName()形式得到其Class对象,如果未指定,则不会进行解析。

<result column="username" jdbcType="VARCHAR" property="username" typeHandler="类全名"/>

我们接着上文的 “构建ResuleMapping对象” 继续看起源码:

public class MapperBuilderAssistant extends BaseBuilder {

    public ResultMapping buildResultMapping(
          Class resultType,
          String property,
          String column,
          Class javaType,
          JdbcType jdbcType,
          String nestedSelect,
          String nestedResultMap,
          String notNullColumn,
          String columnPrefix,
          Class> typeHandler,
          List flags,
          String resultSet,
          String foreignColumn,
          boolean lazy) {
        Class javaTypeClass = resolveResultJavaType(resultType, property, javaType);
        // 尝试通过TypeHandler的Class对象生成实例
        TypeHandler typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
        List composites = parseCompositeColumnName(column);
        // 通过建造者来构建ResultMapping对象
        return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
            .jdbcType(jdbcType)
            .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
            .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
            .resultSet(resultSet)
            .typeHandler(typeHandlerInstance)
            .flags(flags == null ? new ArrayList() : flags)
            .composites(composites)
            .notNullColumns(parseMultipleColumnNames(notNullColumn))
            .columnPrefix(columnPrefix)
            .foreignColumn(foreignColumn)
            .lazy(lazy)
            .build();
  }


    protected TypeHandler resolveTypeHandler(Class javaType, Class> typeHandlerType) {
        if (typeHandlerType == null) {
            return null;
        }
        // 如果当前类型的TypeHandler已经注册过了,那么从注册容器中提取此类型的对象
        TypeHandler handler = typeHandlerRegistry.getMappingTypeHandler(typeHandlerType);
        if (handler == null) {
            // 如果还未注册过此类型对象,实例化一个
            handler = typeHandlerRegistry.getInstance(javaType, typeHandlerType);
        }
        return handler;
  }

}


public final class TypeHandlerRegistry {

    public  TypeHandler getInstance(Class javaTypeClass, Class typeHandlerClass) {
        if (javaTypeClass != null) {
          try {
            Constructor c = typeHandlerClass.getConstructor(Class.class);
            return (TypeHandler) c.newInstance(javaTypeClass);
          } catch (NoSuchMethodException ignored) {
            // ignored
          } catch (Exception e) {
            throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
          }
        }
        try { 
          Constructor c = typeHandlerClass.getConstructor();  // 通过调用其默认构造函数实例化一个TypeHandler对象  
          return (TypeHandler) c.newInstance();
        } catch (Exception e) {
          throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
        }
  }

}

当TypeHandler的Class对象已知时,在构建ResultMapping对象时,优先从注册容器中获取此类型的对象,如果没有,则使用默认构造函数实例化一个。

当然以上的前提是< result>< /result>节点内定义了typeHandler的类名。

我们接着上文的 “通过建造者来构建ResultMapping对象” 接着往下讲:

public class ResultMapping {

    public ResultMapping build() {
        // lock down collections
        resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
        resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
        // 再次解析TypeHandler对象
        resolveTypeHandler();
        validate();
        return resultMapping;
    }

    private void resolveTypeHandler() {
        if (resultMapping.typeHandler == null && resultMapping.javaType != null) {
            Configuration configuration = resultMapping.configuration;
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            // 从typeHandlerRegistry里根据javaType和jdbcType来获取
            resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.javaType, resultMapping.jdbcType);
        }
    }

}

public final class TypeHandlerRegistry {

    public  TypeHandler getTypeHandler(Class type, JdbcType jdbcType) {
        return getTypeHandler((Type) type, jdbcType);
    }

    private  TypeHandler getTypeHandler(Type type, JdbcType jdbcType) {
        // 根据javaType获取对应的集合,因为一类属性可以存储为多种形式,比如数字,可以存储为数字,也可以存储为字符串,还可以存储为日期
        // 只要通过TypeHandler进行转换,就能将javaType的属性转换为多种jdbcType,也就是数据库的字段类型
        // 所以javaType和jdbcType是多对多的关系,也对应着多种TypeHandler
        Map> jdbcHandlerMap = getJdbcHandlerMap(type);
        TypeHandler handler = null;
        if (jdbcHandlerMap != null) {
            // 从Map中获取指定jdbcType对应的TypeHandler对象
            handler = jdbcHandlerMap.get(jdbcType);
            if (handler == null) {
                // 如果此对象不存在,那么获取null类型对应的TypeHandler对象
                handler = jdbcHandlerMap.get(null);
            }
            if (handler == null) {
                handler = pickSoleHandler(jdbcHandlerMap);
            }
        }
        // type drives generics here
        return (TypeHandler) handler;
    }

    // 返回null或者 TYPE_HANDLER_MAP里对应词javaType的集合
    private Map> getJdbcHandlerMap(Type type) {
        Map> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
        if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
          return null;
        }
        if (jdbcHandlerMap == null && type instanceof Class) {
          Class clazz = (Class) type;
          if (clazz.isEnum()) {
            jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(clazz);
            if (jdbcHandlerMap == null) {
              register(clazz, new EnumTypeHandler(clazz));
              return TYPE_HANDLER_MAP.get(clazz);
            }
          } else {
            jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
          }
        }
        TYPE_HANDLER_MAP.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
        return jdbcHandlerMap;
    }

}

从上述代码,我们能明显看到jdbcType在其中发挥的作用。javaType解析时,能够通过属性名称到参数的Class里去搜索对应的javaType,这个是框架会自动做好的,但是jdbcType就不行了,框架并不知道你这个属性对应在数据库中的是什么样的类型,时varchar,还是int,还是timestamp,如果你不定义,那么默认值就是null。

那是不是未定义jdbcType就不需要TypeHandle了吗?不是的,我们接着看源码:

public final class TypeHandlerRegistry {

     public TypeHandlerRegistry() {
        register(Boolean.class, new BooleanTypeHandler());
        register(boolean.class, new BooleanTypeHandler());
        register(JdbcType.BOOLEAN, new BooleanTypeHandler());
        register(JdbcType.BIT, new BooleanTypeHandler());

        register(Byte.class, new ByteTypeHandler());
        register(byte.class, new ByteTypeHandler());
        register(JdbcType.TINYINT, new ByteTypeHandler());

        register(Short.class, new ShortTypeHandler());
        register(short.class, new ShortTypeHandler());
        register(JdbcType.SMALLINT, new ShortTypeHandler());

        //以注册Integer的TypeHandler为例子说明
        register(Integer.class, new IntegerTypeHandler());
        register(int.class, new IntegerTypeHandler());
        register(JdbcType.INTEGER, new IntegerTypeHandler());

        register(Long.class, new LongTypeHandler());
        register(long.class, new LongTypeHandler());

        register(Float.class, new FloatTypeHandler());
        register(float.class, new FloatTypeHandler());
        register(JdbcType.FLOAT, new FloatTypeHandler());

        register(Double.class, new DoubleTypeHandler());
        register(double.class, new DoubleTypeHandler());
        register(JdbcType.DOUBLE, new DoubleTypeHandler());

        ......

    }

 public  void register(Class javaType, TypeHandler typeHandler) {
      register((Type) javaType, typeHandler);
 }

  private  void register(Type javaType, TypeHandler typeHandler) {
      MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
      if (mappedJdbcTypes != null) {
          for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
              register(javaType, handledJdbcType, typeHandler);
          }
          if (mappedJdbcTypes.includeNullJdbcType()) {
              register(javaType, null, typeHandler);
          }
      } else {
          // 一般会走这步,JdbcType 为null
          register(javaType, null, typeHandler);
      }
  }

    private void register(Type javaType, JdbcType jdbcType, TypeHandler handler) {
        if (javaType != null) {
            Map> map = TYPE_HANDLER_MAP.get(javaType);
            if (map == null) {
                map = new HashMap>();
                // 以null为key存储TypeHandler
                TYPE_HANDLER_MAP.put(javaType, map);
            }
            map.put(jdbcType, handler);
        }
        ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
    }

}

从上述代码中可以看出,如果你的javaType是TypeHandlerRegistry实例化时定义过TypeHandler的数据类型,比如那些基本数据类型,那么即使未指定jdbcType,Mybatis也会使用此javaType默认的TypeHander,也就是以 “null” 为jdbcType的TypeHandler。其会负责javaType和jdbcType之间的数据转换。

增删改查的SQL节点的解析也是如此,这里就不再继续说明了。

综上所述,在XML内,尽量定义jdbcType,通过 javaType(Mybatis能通过属性名称和参数Class来确定) 和 jdbcType 就能确定TypeHandler的类型,这也是Mybatis逆向工程生成的Mapper.xml文件中,每种元素节点内都指定了jdbcType的原因。

当然以上只是TypeHander的实现的源码说明,下面我们来看其实际的使用方式:

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        ......
        //注册List类型的TypeHandler
        configuration.getTypeHandlerRegistry().register(List.class, JdbcType.VARCHAR, StringList2VarcharHandler.class);
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setConfiguration(configuration);
        // 匹配多个 MapperConfig.xml, 使用mappingLocation属性
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/bob/web/mvc/mapper/*Mapper.xml"));
        return sqlSessionFactoryBean.getObject();
    }

在通过DataSource 定义SqlSessionFactory 时,显式的通过Configuration来获取注册器TypeHandlerRegistry,然后注册TypeHandler。
注册的形式有很多种,指定此TypeHandler对应的javaType,jdbcType;或者全部不指定,由Mybatis根据javaType来提取,当然前提是javaType不是Mybatis内置的类型。


public final class TypeHandlerRegistry {
    private void register(Type javaType, JdbcType jdbcType, TypeHandler handler) {
        if (javaType != null) {
          Map> map = TYPE_HANDLER_MAP.get(javaType);
          if (map == null) {
            map = new HashMap>();
            TYPE_HANDLER_MAP.put(javaType, map);
          }
          map.put(jdbcType, handler);
        }
        ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
    }
}

通过手动注册TypeHandler,就注册到了TypeHandlerRegistry ,这样Mybatis在解析XML元素时就能获取到相应的TypeHandler,也就是说在XML元素内不需要手动指定typeHandler=“……”这种形式了,对开发还是有一定的方便的。

你可能感兴趣的:(mybatis,mybatis)