myBatis源码解析-类型转换篇(5)

前言

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

 

myBatis源码解析-类型转换篇(5)_第1张图片

 

 

 源码解析

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

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

myBatis源码解析-类型转换篇(5)_第2张图片

 

 

 分析BaseTypeHandle前,分析其接口TypeHandle。

// 类型处理器接口,查询参数时将java类型转为jdbc类型。获取结果时将jdbc类型转问java类型。
public interface TypeHandler {
  // 设置查询参数,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的原生类型。

public abstract class TypeReference {

  private final Type rawType;   // 保存所处理的java原生类型、个人理解即T泛型的类型。

  protected TypeReference() {
    rawType = getSuperclassTypeParameter(getClass());
  }
  // rawType的获取过程。任务类型处理器都需要继承BaseTypeHandle,而BaseTypeHandle继承TypeReference,此处为了获取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的java类型。
      rawType = ((ParameterizedType) rawType).getRawType();
    }

    return rawType;
  }
  
  public final Type getRawType() {
    return rawType;  // 返回解析的rawType
  }
  .....
}

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

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

// 类型处理器基类,增加了输入参数为null时的处理
public abstract class BaseTypeHandler extends TypeReference implements TypeHandler {

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

public class DateTypeHandler extends BaseTypeHandler { // 日期转换处理器,泛型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> reversePrimitiveMap = new HashMap, 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> JDBC_TYPE_HANDLER_MAP = new EnumMap>(JdbcType.class);
  // java类型处理器map集合,由此可知,一个java类型可以对应对个 jdbc类型
  private final Map>> TYPE_HANDLER_MAP = new HashMap>>();
  // 默认的未知类型处理器
  private final TypeHandler UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
  // 所有类型的处理器
  private final Map, TypeHandler> ALL_TYPE_HANDLERS_MAP = new HashMap, TypeHandler>(); 
  
 

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

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

// java type + jdbc type + handler
public  void register(Class type, JdbcType jdbcType, TypeHandlerextends 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> map = TYPE_HANDLER_MAP.get(javaType);
      if (map == null) { // 若为空,则建立一个,并将此 jdbc+handle 集合放入TYPE_HANDLER_MAP中
        map = new HashMap>();
        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);
  }

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

myBatis源码解析-类型转换篇(5)_第3张图片

 

 

 2.2 java类型+处理器方法

public  void register(Class javaType, TypeHandlerextends T> typeHandler) {
    register((Type) javaType, typeHandler);
  }

  private  void register(Type javaType, TypeHandlerextends 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  void register(TypeHandler 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。前文分析了,任何一个类型注册器都会继承TypeReference,所以后面的判断条件会为true
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {  
      try {
        TypeReference typeReference = (TypeReference) typeHandler;
        register(typeReference.getRawType(), typeHandler);  // 调用typeReference.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) null, typeHandler); // 调用java类型+处理器类型的注册方法
    }
  }

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

总结

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

你可能感兴趣的:(myBatis源码解析-类型转换篇(5))