美文网首页
MyBatis源码解析 - 类型转换模块

MyBatis源码解析 - 类型转换模块

作者: janker0718 | 来源:发表于2020-03-12 09:44 被阅读0次

    MyBatis源码解析 - 类型转换模块

    前言

    JDBC数据类型与Java语言中的数据类型并不是完全对应的,所以在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换成JDBC类型,而从结果集中获取数据时,则需要从JDBC类型转换成Java类型。MyBatis 使用类型处理器完成上述两种转换,如图所示。

    在MyBatis中使用JdbeType这个枚举类型代表JIDBC中的数据类型,该枚举类型中定义了TYPECODE字段,记录了JDBC类型在javasql.Types中相应的常量编码,并通过一个静态集合codelookup(HashMap<nteger,JdbeTypec>类型)维护了常量编码与JdbcType之间的对应关系。

    TypeHandler

    TypeHandler是类型处理器接口,MyBatis中所有的类型转换器都继承了TypeHandler接口,在TypeHandler接口中定义了如下四个方法,这四个方法分为两类:setParameter()方法负责将数据由JdbeType类型转换成Java类型;getResult()方法及其重载负责将数据由Java类型转换成JdbcType类型。

    public interface TypeHandler<T> {
    
      //在通过PreparedStatement为SQL语句绑定参数时,会将数据由JdbcType类型转换成Java类型
      void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
    
      //从 ResultSet 中获取数据时会调用此方法,会将数据由Java类型转换成JdbcType类型
      T getResult(ResultSet rs, String columnName) throws SQLException;
    
      T getResult(ResultSet rs, int columnIndex) throws SQLException;
    
      T getResult(CallableStatement cs, int columnIndex) throws SQLException;
    
    }
    

    为方便用户自定义TypeHandler实现,MyBatis 提供了BaseTypeHandler 这个抽象类,它实现了TypeHandler接口,并继承了TypeReference抽象类,其继承结构如图所示。

    <img src="http://qiniu-cdn.janker.top/oneblog/20200117165004531.jpg" style="zoom:33%;" />

    BaseTypeHandler中实现TypeHandler.setParameter()方法和TypeHandler.getResult()方法, 具体实现如下所示。需要注意的是,这两个方法对于非空数据的处理都交给了子类实现。

    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
      if (parameter == null) {
        if (jdbcType == null) {
          //参数 类型 异常
          throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
        }
        try {
          //绑定参数为null的处理
          ps.setNull(i, jdbcType.TYPE_CODE);
        } catch (SQLException e) {
          throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
                + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
                + "Cause: " + e, e);
        }
      } else {
        try {
          //绑定非空参数 该方法为抽象方法 由子类实现
          setNonNullParameter(ps, i, parameter, jdbcType);
        } catch (Exception e) {
          throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
                + "Try setting a different JdbcType for this parameter or a different configuration property. "
                + "Cause: " + e, e);
        }
      }
    }
    
    public T getResult(ResultSet rs, String columnName) throws SQLException {
      try {
        //抽象方法 由多个重载 由子类实现
        return getNullableResult(rs, columnName);
      } catch (Exception e) {
        throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
      }
    }
    

    BaseTypeHandler的实现类比较多,如图所示,但大多是直接调用PreparedStatementResultSetCallableStatement的对应方法,实现比较简单。

    <img src="/Users/janker/Library/Application Support/typora-user-images/image-20200118165213276.png" style="zoom:50%;" />

    这里以IntegerTypeHandler为例简单介绍,其他的实现类请读者参考相关源码:

    public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
    
      @Override
      public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
          throws SQLException {
        //调用PreparedStatement.setInt()实现参数绑定
        ps.setInt(i, parameter);
      }
    
      @Override
      public Integer getNullableResult(ResultSet rs, String columnName)
          throws SQLException {
        //调用PreparedStatement.getInt()获取指定列值
        int result = rs.getInt(columnName);
        return result == 0 && rs.wasNull() ? null : result;
      }
    
      @Override
      public Integer getNullableResult(ResultSet rs, int columnIndex)
          throws SQLException {
        //调用ResultSet.getInt()获取指定列值
        int result = rs.getInt(columnIndex);
        return result == 0 && rs.wasNull() ? null : result;
      }
    
      @Override
      public Integer getNullableResult(CallableStatement cs, int columnIndex)
          throws SQLException {
        //调用CallableStatement.getInt()获取指定列值
        int result = cs.getInt(columnIndex);
        return result == 0 && cs.wasNull() ? null : result;
      }
    }
    

    一般情况下, TypeHandler用于完成单个参数以及单个列值的类型转换,如果存在多列值转换成一个Java对象的需求,应该优先考虑使用在映射文件中定义合适的映射规则(<resultMap>节点)完成映射。

    TypeHandlerRegistry

    介绍完TypeHandler接口及其功能后,我们来看一下MyBatis如何管理众多的TypeHandler接口实现,如何知道何时使用哪个TypeHandler接口完成转换。这些都是由TypehandlerRegistry完成的,在MyBatis初始化过程中,会为所有已知的TypeHandler创建对象,并实现注册到TypeHandlerRegistry中,由TypeHandlerRegistry负责挂你这些TypeHandler对象。

    TypeHandlerRegistry的核心字段的含义如下:

    //记录JdbcType与TypeHandler之间的对应关系,其中JdbcType是一个枚举类型,它定义对应的JDBC类型
    //该集合主要用于从结果集读取数据时,将数据从Jdbc类型转换成Java类型
    private final Map<JdbcType, TypeHandler<?>>  jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
    //记录了Java类型向指定JdbcType转换时,需要使用的TypeHandler对象。例如: Java 类型中的String可能
    //转换成数据库的char、varchar等多种类型,所以存在一对多关系
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
    private final TypeHandler<Object> unknownTypeHandler;
    //记录了全部TypeHandler类型以及该类型相应的TypeHandler对象
    private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
    
    //空TypeHandler集合的标识
    private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
    
    private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
    

    1. 注册TypeHandler对象

    TypeHandlerRegistry.register()方法实现了注册TypeHandler对象的功能,该注册过程会向上述四个集合中添加TypeHandler对象。register()方法有多个重载,这些重载之间的调用关系如图所示。

    <img src="http://qiniu-cdn.janker.top/oneblog/20200119114322840.jpg" style="zoom:33%;" />

    从图中可以看出,多数register()方法最终会调用重载④完成注册功能,我们先来介绍该方法的实现,其三个参数分别指定了TypeHandler 能够处理的Java 类型、Jdbc 类型以及TypeHandler对象。

    //register()方法的重载④的实现如下:
    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
      if (javaType != null) { //检验是否明确指定了TypeHandler能够处理的Java类型
        //获取指定Java类型在TYPE_ HANDLER_ MAP集合中对应的TypeHandler集合
        Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
        if (map == null || map == NULL_TYPE_HANDLER_MAP) {
          //创建新的TypeHandler集合,并添加到TYPE HANDLER MAP中
          map = new HashMap<>();
          typeHandlerMap.put(javaType, map);
        }
        //将TypeHandler对象注册到typeHandlerMap集合中
        map.put(jdbcType, handler);
      }
      //向AllTypeHandler集合注册TypeHandler类型和对应的TypeHandler对象【以Class为key handler对象为value】
      allTypeHandlersMap.put(handler.getClass(), handler);
    }
    

    在①~③这个三个registr()方法重载中会尝试读取TypeHandler类中定义的@MappedTypes注解和@MappedJdbcTypes注解,@MappedTypes 注解用于指明该TypeHandler实现类能够处理的Java类型的集合,@MappedJdbcTypes注解用于指明该TypeHandler实现类能够处理的JDBC类型集合。register()方法的重载①~③的具体实现如下:

    //register()方法的重载①的实现如下:
    public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    //获取 @MappedTypes 注解
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> javaTypeClass : mappedTypes.value()) {
        //经过强制类型转换 以及反射创建TypeHandler对象之后,交由重载③继续处理
        register(javaTypeClass, typeHandlerClass);
        mappedTypeFound = true;
      }
    }
    if (!mappedTypeFound) {
      //未@MappedTypes 注解,调用重载方法 ② 处理
      register(getInstance(null, typeHandlerClass));
    }
    }
    //register()方法的重载②的实现如下:
    public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    //获取@MappedTypes注解 并根据MappedTypes注解指定的java类型进行注册,逻辑与重载①类似
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> handledType : mappedTypes.value()) {
        //交由重载③进行处理
        register(handledType, typeHandler);
        mappedTypeFound = true;
      }
    }
    //从3.1.0版本开始,可以根据TypeHandler类型自动查找对应的Java类型,这需要
    //我们的TypeHandler实现类同时继承TypeReference这个抽象类
    // @since 3.1.0 - try to auto-discover the mapped type
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
      try {
        TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
        //交由重载③进行处理
        register(typeReference.getRawType(), typeHandler);
        mappedTypeFound = true;
      } catch (Throwable t) {
        // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
      }
    }
    if (!mappedTypeFound) {
      //类型转换后,交由重载③继续处理
      register((Class<T>) null, typeHandler);
    }
    }
    //register()方法的重载③的实现如下:
    private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    //获取MappedJdbcTypes注解
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
      //根据指定类型进行注册 读取@MappedJdbcTypes value值 获取handlerJdbcType
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
        register(javaType, handledJdbcType, typeHandler);
      }
      if (mappedJdbcTypes.includeNullJdbcType()) {
        register(javaType, null, typeHandler);
      }
    } else {
      //未指定MappedJdbcTypes注解 交由重载④继续处理
      register(javaType, null, typeHandler);
    }
    }
    

    上述全部的register()方法重 载都是在向TYPE_ HANDLER_ _MAP集合和ALL_TYPE_HANDLERS MAP集合注册TypeHandler 对象,而重载⑤是向JDBC_ TYPE_ HANDLER_ MAP集合注册TypeHandler对象,其具体实现如下:

    //register()方法重载⑤的实现如下:
    public void register(JdbcType jdbcType, TypeHandler<?> handler) {
    // 注册JdbcType类型对一个的TypeHandler
    jdbcTypeHandlerMap.put(jdbcType, handler);
    }
    

    TypeHandlerRegistry除了提供注册单个TypeHandlerregister()重载,还可以扫描整个包下的TypeHandler接口实现类,并将完成这些TypeHandler实现类的注册。下面来看重载⑥的具体实现:

    //register()方法重载⑥的实现,主要用来自动扫描指定包下的TypeHandler实现并完成注册
    public void register(String packageName) {
      ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
      //查找指定包下的TypeHandler接口实现类
      resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
      Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
      for (Class<?> type : handlerSet) {
        //Ignore inner classes and interfaces (including package-info.java) and abstract classes
        //过滤调内部类、接口以及抽象类
        if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
          //交由重载①继续后面注册操作
          register(type);
        }
      }
    }
    

    最后来看TypeHandlerRegistry构造方法,会通过上述register()方法为很多基础类型注册对应的TypeHandler对象,简略代码如下:

    public TypeHandlerRegistry(Configuration configuration) {
        //...这里重点看下String相关的TypeHandler对象的注册 其他类型的TypeHandler的注册类型(略)
        // stringTypeHandler 能够将数据从String 类型转换成null ( JdbcType ),所以向
        TYPE HANDLER MAP
        //集合注册该对象,并向ALL_ TYPE_ HANDLERS_ MAP集合注册StringTypeHandler
        register(String.class, new StringTypeHandler());
        
        register(JdbcType.NVARCHAR, new NStringTypeHandler());
        //向JDBC_TYPE_HANDLER_MAP集合注册NVARCHAR对应的NStringTypeHandler
      register(JdbcType.NVARCHAR, new NStringTypeHandler());
      // ...注册其他JdbcType类型对应的TypeHandler的过程类似(略)
    }
    

    2. 查找TypeHandler对象

    介绍完注册TypeHandler对象的功能之后,再来介绍TypeHandlerRegistry提供的查找TypeHandler对象的功能。TypeHandlerRegistry.getTypeHandler()方法实现了从上述四个集合中获取对应TypeHandler对象的功能。TypeHandlerRegistry.getTypeHandler()方法有多个重载,这些重载之间的关系如图所示。

    <img src="http://qiniu-cdn.janker.top/oneblog/20200119134802598.png" style="zoom:40%;" />

    经过一系列类型转 换之后,TypeHandlerRegistry.getTypeHandler()方法的多个重载都会调用TypeHandlerRegistry.getTypeHandle(Type,JdbcType)这个重载方法,它会根据指定的Java类型和JdbcType类型查找相应的TypeHandler对象,具体实现如下:

    private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
      if (ParamMap.class.equals(type)) {
        return null;
      }
      //查找或初始化Java类型对一个的TypeHandler对象
      Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
      TypeHandler<?> handler = null;
      if (jdbcHandlerMap != null) {
        //根据JdbcType查找TypeHandler
        handler = jdbcHandlerMap.get(jdbcType);
        if (handler == null) {
          handler = jdbcHandlerMap.get(null);
        }
        if (handler == null) {
          // #591
          //如果JdbcType只注册了一个TypeHandler,使用此TypeHandler对象
          handler = pickSoleHandler(jdbcHandlerMap);
        }
      }
      // type drives generics here
      return (TypeHandler<T>) handler;
    }
    

    TypeHandlerRegitry.getJdbcHandlerMap()方法中,会检测TYPE_HANDLER_MAP集合中指定Java类型对应的TypeHandler集合是否已经初始化。如果未初始化,则尝试以该Java类型的、已初始化的父类对应的TypeHandler集合为初始集合;如不存在己初始化的父类,则将其对应的TypeHandler集合初始化为NULL_TYPE_HANDLER_MAP标识。getJdbcHandlerMap()方法具体实现如下:

    private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
        //查找指定Java类型对应的TypeHandler集合
        Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
        if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) { //检测是否为空集合标识
          return null;
        }
        //初始化指定的Java类型的TypeHandler集合
        if (jdbcHandlerMap == null && type instanceof Class) {
          Class<?> clazz = (Class<?>) type;
          if (Enum.class.isAssignableFrom(clazz)) {
            Class<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;
            jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass);
            if (jdbcHandlerMap == null) {
              //枚举类型的处理
              register(enumClass, getInstance(enumClass, defaultEnumTypeHandler));
              return typeHandlerMap.get(enumClass);
            }
          } else {
            //查找父类对应的TypeHandler集合,并作为初始化集合
            jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
          }
        }
        // 以NULL_TYPE_HANDLER_MAP作为TypeHandler集合
        typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
        return jdbcHandlerMap;
      }
      
      // getJdbcHandlerMapForSuperclass()实现
      private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
        Class<?> superclass =  clazz.getSuperclass();
        if (superclass == null || Object.class.equals(superclass)) {
          return null;  //父类为Object或null则查找结束
        }
        Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(superclass);
        if (jdbcHandlerMap != null) {
          return jdbcHandlerMap;
        } else {
          //继续递归查找父类对应的TypeHandler集合
          return getJdbcHandlerMapForSuperclass(superclass);
        }
      }
    

    TypeHandlerRegistry.getMappingTypeHandler()方法会根据指定的TypeHandler类型,直接从ALL_TYPE_HANDLERS_MAP集合中查找TypeHandler对象。TypeHandlerRegistry.getTypeHandler(JdbcType)方法会根据指定的JdbcType类型,从JDBC_TYPE_HANDLER_MAP集合中查找TypeHandler对象。这两个方法实现相对简单,代码就不贴出来了。
    最后,除了MyBatis本身提供的TypeHandler实现,我们也可以添加自定义的TypeHandler
    接口实现,添加方式是在mybatisconfig.xml配置文件中的<typeHandlers>节点下,添加相应的<typeHandler>节点配置,并指定自定义的TypeHandler接口实现类。在MyBatis初始化时会解析该节点,并将该TypeHandler类型的对象注册到TypeHandlerRegistry中,供MyBatis后续使用。在后面介绍MyBatis初始化时还会提到该配置。

    TypeAliasRegistry

    在编写SQL语句时,使用别名可以方便理解以及维护,例如表名或列名很长时,我们一般会为其设计易懂易维护的别名。MyBatis将SQL语句中别名的概念进行了延伸和扩展,MyBatis可以为一个类添加一一个别名,之后就可以通过别名引用该类。MyBatis通过TypeAliasRegistry类完成别名注册和管理的功能,TypeAliasRegistry的结构比较简单,它通过TYPE_ ALIASES字段(Map<String, Class<?>>类型)管理别名与Java类型之间的对应关系,通过TypeAliasRegistry.registerAlias()方法完成注册别名,该方法的具体实现如下:

    public void registerAlias(String alias, Class<?> value) {
      if (alias == null) {  //校验别名是否为空,为空则抛出类型异常
        throw new TypeException("The parameter alias cannot be null");
      }
      // issue #748
      //将别名转换为小写
      String key = alias.toLowerCase(Locale.ENGLISH);
      //校验别名是否已经存在
      if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
        throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
      }
      //注册别名
      typeAliases.put(key, value);
    }
    

    TypeAliasRegistry的构造方法中,默认为Java的基本类型及其数组类型、基本类型的封装类及其数组类型、DateBigDecimalBigIntegerMapHashMapListArrayListCollectionIteratorResultSet等类型添加了别名,代码比较简单,请读者参考TypeAliasRegistry的源码进行学习。

    TypeAliasRegistry中还有两个方法需要介绍一下,registerAliases(String,Class<?>)重载会扫描指定包下所有的类,并为指定类的子类添加别名; registerAlias(Class<?>)重 载中会尝试读取@Alias注解。这两个方法的实现如下:

    public void registerAliases(String packageName, Class<?> superType) {
      ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
      //查找指定包下的superType类型类
      resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
      Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
      for (Class<?> type : typeSet) {
        // Ignore inner classes and interfaces (including package-info.java)
        // Skip also inner classes. See issue #6
        //过滤调内部类、接口、抽象类
        if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
          registerAlias(type);
        }
      }
    }
    
    public void registerAlias(Class<?> type) {
      String alias = type.getSimpleName();  //类型的简单名称(不包含报名)
      //读取Alias注解
      Alias aliasAnnotation = type.getAnnotation(Alias.class);
      if (aliasAnnotation != null) {
        alias = aliasAnnotation.value();
      }
      //检测次别名不存在后,会将其记录到TYPE_ALIASES集合中
      registerAlias(alias, type);
    }
    

    本文由 Janker 创作,采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
    <img src="http://qiniu-cdn.janker.top/oneblog/20200311150833864.jpg" style="zoom:50%;" />

    相关文章

      网友评论

          本文标题:MyBatis源码解析 - 类型转换模块

          本文链接:https://www.haomeiwen.com/subject/czfyjhtx.html