zoukankan      html  css  js  c++  java
  • myBatis源码解析-类型转换篇(5)

    前言

    开始分析Type包前,说明下使用场景。数据构建语句使用PreparedStatement,需要输入的是jdbc类型,但我们一般写的是java类型。同理,数据库结果集返回的是jdbc类型,而我们需要java类型。这就涉及到一个类型转换问题,Type包就是解决这个问题。下面是Type包类图所在结构:

     源码解析

    1. BaseTypeHandle<T> - 类型处理器实现的基类

    mybatis中的默认类型处理器,自定义类型处理器都继承自BaseTypeHandle。是分析类型处理器的关键,查看其类图如下:

     分析BaseTypeHandle<T>前,分析其接口TypeHandle。

    // 类型处理器接口,查询参数时将java类型转为jdbc类型。获取结果时将jdbc类型转问java类型。
    public interface TypeHandler<T> {
      // 设置查询参数,java类型转为jdbc类型
      void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
      // 获取结果参数,jdbc转为java类型
      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接口定义了参数转换的方法。查询时将java类型转为jdbc类型,获取结果时将jdbc类型转为java类型。

    分析继承的抽象类,TypeReference<T>,主要是获取泛型T的原生类型。

    public abstract class TypeReference<T> {
    
      private final Type rawType;   // 保存所处理的java原生类型、个人理解即T泛型的类型。
    
      protected TypeReference() {
        rawType = getSuperclassTypeParameter(getClass());
      }
      // rawType的获取过程。任务类型处理器都需要继承BaseTypeHandle<T>,而BaseTypeHandle<T>继承TypeReference<T>,此处为了获取T的java类型。
      // 至于为什么使用这一变量,因为我们自定义类型处理器可以不指定java类型,只指定jdbc类型,这样java类型默认就是T类型。
      Type getSuperclassTypeParameter(Class<?> clazz) {
        Type genericSuperclass = clazz.getGenericSuperclass();  // 获取分类,包括T。此处和getSuperClass的区别是,getSuperClass只返回直接父类,并不包括父类带的泛型T
        if (genericSuperclass instanceof Class) {  // 任何类型处理器都有泛型T,一直循环找,如果没找到,直接报错。
          // try to climb up the hierarchy until meet something useful
          if (TypeReference.class != genericSuperclass) {
            return getSuperclassTypeParameter(clazz.getSuperclass());
          }
    
          throw new TypeException("'" + getClass() + "' extends TypeReference but misses the type parameter. "
            + "Remove the extension or add a type parameter to it.");
        }
    
        Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; // 获取泛型T的java类型
        // TODO remove this when Reflector is fixed to return Types
        if (rawType instanceof ParameterizedType) {  // ?自己debug没有进入到这一步,保留。个人理解是万一还是类型嵌套模式如user<T>,还有泛型T。就再次获取T的java类型。
          rawType = ((ParameterizedType) rawType).getRawType();
        }
    
        return rawType;
      }
      
      public final Type getRawType() {
        return rawType;  // 返回解析的rawType
      }
      .....
    }

    TypeReference<T>类提供了获取rawType的方法。对于我们自定义类型转换器,可以只输入要转换的jdbc类型,那默认的待转换java类型就是rawType类型。

    接下来分析BaseTypeHandle<T>,也是一个抽象类,查看其源码,主要实现了对输入参数,结果参数为空的处理,若不为空,则交给子类具体实现。

    // 类型处理器基类,增加了输入参数为null时的处理
    public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
    
      protected Configuration configuration;  // 全局配置参数
    
      public void setConfiguration(Configuration c) {
        this.configuration = c;
      }
    
      public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {   // 对输入参数为null时的处理
          if (jdbcType == null) {  // jdbcType不能为空
            throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
          }
          try {
            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 {
          setNonNullParameter(ps, i, parameter, jdbcType); // 输入参数不为空的处理
        }
      }
      .......
      // 对非空参数需要子类实现。
      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;

    BaseTypeHandler<T>其实只对空数据进行处理,非空数据交给子类实现,此处使用了模板设计模式。查看具体的类型处理器,如DateTypeHandle进行验证。

    public class DateTypeHandler extends BaseTypeHandler<Date> { // 日期转换处理器,泛型T为java.util.date
    
      @Override
      public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) 
          throws SQLException {
        ps.setTimestamp(i, new Timestamp((parameter).getTime()));  // 设置PreparedStatement,其实就是java类型转为数据库识别的jdbc类型
      }
    
      @Override
      public Date getNullableResult(ResultSet rs, String columnName)
          throws SQLException {
        Timestamp sqlTimestamp = rs.getTimestamp(columnName); // 获取指定列的结果
        if (sqlTimestamp != null) {
          return new Date(sqlTimestamp.getTime());  // 获取Date结果,jdbc类型转为java类型。
        }
        return null;
      }

    类型处理器实现也很简单,是将java数据类型和数据库识别的jdbc类型的相互转换。其余的类型处理器太多了,暂且不分析了,大家感兴趣的可以自己看下。

    2. TypeHandleRegistry - 类型处理器注册类

    类型转换处理器定义完毕后,需要有个仓库进行注册,后续使用直接去拿即可。TypeHandleRegistry就是处理器注册的地方。现在对TypeHandleRegistry源码进行分析。

    private static final Map<Class<?>, Class<?>> reversePrimitiveMap = new HashMap<Class<?>, Class<?>>() {
        private static final long serialVersionUID = 1L;
        {
          put(Byte.class, byte.class);
          put(Short.class, short.class);
          put(Integer.class, int.class);
          put(Long.class, long.class);
          put(Float.class, float.class);
          put(Double.class, double.class);
          put(Boolean.class, boolean.class);
          put(Character.class, char.class);
        }
      };
      // 数据库类型处理器map集合,jdbc类型为key,TypeHandle为value
      private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
      // java类型处理器map集合,由此可知,一个java类型可以对应对个 jdbc类型
      private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>();
      // 默认的未知类型处理器
      private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
      // 所有类型的处理器
      private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

    分析完具体属性后,看具体注册方法。TypeHandleRegistry支持的注册方法有多种,重写的register()方法就有很多种。一般注册我们需要提供转换的java类型和jdbc类型,具体的转换器。mybatis除了支持这种默认的注册方式外,还提供了如java类型+处理器,jdbc类型+处理器,单个处理器四种处理方法,一个个分析。

    2.1 java类型+jdbc类型+处理器方法

    // java type + jdbc type + handler
    public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
        register((Type) type, jdbcType, handler);
      }
    
      private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {  // 根据java类型,jdbc类型,typeHandle来注册
        if (javaType != null) {  // 若对应的java类型不为空
         // 获取java类型对应的 jdbc+handle 集合
          Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
          if (map == null) { // 若为空,则建立一个,并将此 jdbc+handle 集合放入TYPE_HANDLER_MAP中
            map = new HashMap<JdbcType, TypeHandler<?>>();
            TYPE_HANDLER_MAP.put(javaType, map);
          }
          map.put(jdbcType, handler);
          // 如果当前的java类型是基本数据类型的包装类(Integer,Long等),则将其对应的基本数据类型(int,long等)也注册进去
          if (reversePrimitiveMap.containsKey(javaType)) {
            register(reversePrimitiveMap.get(javaType), jdbcType, handler);
          }
        }
        ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
      }

    流程很简单,其余的注册方法基本会调用这个方法。流程如下: 

     2.2 java类型+处理器方法

    public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
        register((Type) javaType, typeHandler);
      }
    
      private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
        // 获取类型处理器中@MappedJdbcTypes注解,该注解作用是定义类型处理器所处理的jdbc类型列表
        MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
        if (mappedJdbcTypes != null) { // 若没有定义,则默认为空
          for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { // 一个java类型可对应多个jdbc类型。
            register(javaType, handledJdbcType, typeHandler);
          }
          if (mappedJdbcTypes.includeNullJdbcType()) {
            register(javaType, null, typeHandler);
          }
        } else {
          register(javaType, null, typeHandler);
        }
      }

    java类型+处理器类型其实也是调用java类型+jdbc类型+处理器注册方法。只是提供了一个获取@MappedJdbcTypes注解的功能。此注解用在自定义处理器类,用来获取处理器指定的jdbc类型。

    2.3 jdbc类型+处理器方法

      public void register(JdbcType jdbcType, TypeHandler<?> handler) {
        JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);  // 在map中添加条记录而已
      }

    jdbc类型+处理器方法实际上只是在jdbc与处理器关联的map中放了条记录。

    2.4 只提供处理器

    // Only handler
    
      @SuppressWarnings("unchecked")
      public <T> void register(TypeHandler<T> typeHandler) {
        boolean mappedTypeFound = false;
        // 获取java类型,我们在自定义类型处理器使用@MappedTypes注解来定义我们要转换的java类型
        MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
        if (mappedTypes != null) { // 若定义了转换的java类型
          for (Class<?> handledType : mappedTypes.value()) { // 则遍历java类型的列表,在调用java类型+处理器类型的注册方法
            register(handledType, typeHandler);
            mappedTypeFound = true; // 找到了待转换的java类型
          }
        }
        // @since 3.1.0 - try to auto-discover the mapped type
        // 若没找到待注册的java类型且继承了TypeReference<T>。前文分析了,任何一个类型注册器都会继承TypeReference<T>,所以后面的判断条件会为true
        if (!mappedTypeFound && typeHandler instanceof TypeReference) {  
          try {
            TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
            register(typeReference.getRawType(), typeHandler);  // 调用typeReference<T>.getRawType()其实获取的是泛型T的类型
            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); // 调用java类型+处理器类型的注册方法
        }
      }

    此方法一般用于用户注册自定义处理器。综上分析,用户自己实现BaseTypeHandle<T>时,可以使用@MappedTypes注解来指定要转换的java类型,若没有指定,则默认为TypeReference<T>泛型T的java类型。也可以使用@MappedJdbcTypes注解来指定要转换的jdbc类型,若没有指定,则默认为null。这也是为什么网上很多自定义类型处理器有的使用了注解,有的没有使用注解。此处,算是做了一个解释。另外还需注意的是,通过JDBC_TYPE_HANDLER_MAP这一属性可知,对于一个java类型,其实支持多种jdbc类型与之对应。

    总结

    对于类型处理器这块,总体来说比较简单,分析起来较轻松。本来还打算写一篇事务包的分析,但发现mybatis其实自身没有实现任务事务的操作。仅是对数据库本身事务的简单封装。接下来准备开始分析session,execute数据库执行过程了,还得慢慢来。如觉得还可以,还请看官们点个赞支持下。

  • 相关阅读:
    通过Math.atan2计算角度 改变物体朝向
    table.sort 排序的问题
    shader 实现正旋波效果 水面波动效果
    第一篇碎碎心得
    ping 整理
    路由器
    C语言里如何读取位数据的某几位?
    ubunut命令
    基于SPIflash 的fatfs调试步骤
    makefile 学习总结--函数
  • 原文地址:https://www.cnblogs.com/xiaobingblog/p/13502848.html
Copyright © 2011-2022 走看看