Mybatis源码学习(10)-类型处理器之TypeHandler、BaseTypeHandler、UnknownTypeHandler等

一、概述

  JDBC数据类型与Java 语言中的数据类型井不是完全对应的,所以在执行SQL查询的时候,需要把参数从Java类型转成JDBC类型,在处理结果集的时候,需要把结果集中的JDBC数据类型转换成对应的Java类型。

二、类详解

  Mybatis类型处理相关代码主要在org.apache.ibatis.type包中。主要包括了以下类型,其中*TypeHandler代表了不同类型的类型处理器。
在这里插入图片描述

  1. Alias 注解类
    别名注解
  2. TypeHandler 接口
    类型处理器接口
  3. TypeReference 抽象类
    用于获取原生类型(基本类型)
  4. BaseTypeHandler 抽象类
    类型处理器基类
  5. *TypeHandler 类型处理器实现类
    不同数据类型对应不同的类型处理器
  6. JdbcType 枚举类
    JdbcType枚举类,对应数据库表中字段的类型
  7. MappedJdbcTypes 注解类
    用于指明类型处理器可以处理的JdbcType中的类型集合
  8. MappedTypes 注解类
    用于指明该TypeHandler实现类能够处理的Java 类型的集合
  9. SimpleTypeRegistry 注册类
    用于判断哪些类是简单类型,和JAVA的基本数据类型不完全一样
  10. TypeAliasRegistry 类别名注册器
    用于取代复杂的类型全限定名,mybatis中用于映射器配置文件中进行参数类型与返回结果类型的设置。默认完成了大量基础类型别名的注册。
  11. TypeException 异常类
  12. TypeHandlerRegistry 类型处理器注册器
    主要完成类型处理器的注册功能,同时也能对类型处理器进行统筹管理,其内部定义了集合来进行类型处理器的存取,同时定义了存取方法。默认完成了大量常见类型处理器的注册。
1、TypeHandler类

  JTypeHandler接口是所有类型转换器的接口,主要实现了四个方法,分为两类,其中,setParameter()方法负责将数据由JdbcType类型转换成Java类型: getResult()方法及其重载负责将数据由Java 类型转换成JdbcType类型。每个方法具体的作用,可以参考下面方法中的注释。

/**
 * 类型处理器
 * @author Clinton Begin
 */
public interface TypeHandler<T> {

  /**
   * 设置参数(设置数据库操作的参数,例如查询参数、删除参数、更新参数等)
   * 
   * @param ps
   * @param i
   * @param parameter
   * @param jdbcType
   * @throws SQLException
   */
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  /**
   * 取得结果,供普通select用
   * 通过列名(columnName)从结果集中获取结果
   * @param rs
   * @param columnName
   * @return
   * @throws SQLException
   */
  T getResult(ResultSet rs, String columnName) throws SQLException;
  /**
   * 取得结果,供普通select用
   * 通过列下标(columnIndex)从结果集中获取结果
   * @param rs
   * @param columnIndex
   * @return
   * @throws SQLException
   */
  T getResult(ResultSet rs, int columnIndex) throws SQLException;
  /**
   * 取得结果,供存储过程使用
   * 针对存储过程而设,通过列下标的方式来获取存储过程输出结果中的数据
   * @param cs
   * @param columnIndex
   * @return
   * @throws SQLException
   */
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

2、TypeReference类

  类型引用,用于获取泛型类中的原生类型,Java中的原生类型又称为基本类型,即byte、short、int、long、float、double、boolean、char八大基本数据类型。其中,getRawType()方法,用于返回引用的原生类型,该类主要对外提供的方法;getSuperclassTypeParameter()方法,主要是内部处理逻辑,主要用于获取当前泛型类(可能是在父类中使用了泛型类)中对应的原生类型,并赋值给属性rawType。

例如:在下面代码中,CustomStringTypeHandler 类继承了StringTypeHandler处理器,然后StringTypeHandler 处理器又继承了BaseTypeHandler<String>处理器,所以CustomStringTypeHandler类的实例对象,调用getRawType()方法的时候,就会返回String.class类型。

new CustomStringTypeHandler().getRawType()
public class StringTypeHandler extends BaseTypeHandler<String> {
  //省略
}
public static final class CustomStringTypeHandler extends StringTypeHandler {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
      // do something
      super.setNonNullParameter(ps, i, parameter, jdbcType);
    }

  }

注:getSuperclassTypeParameter()方法的详细逻辑,可以参考下面博客:https://www.cnblogs.com/V1haoge/p/6715063.html

public abstract class TypeReference<T> {

  /**
   * 引用的原生类型
   * 该类就是为了持有这个具体的类型处理器所处理的Java类型的原生类型。我们可以看到在该类中还有两个方法getRawType()和toString()方法,
   * 这两个方法都是public修饰的,是对外公开的方法, 那么也就意味着这个原生类型是为了被外部调用而设。
   */
  private final Type rawType;

  protected TypeReference() {
	//参数getClass(),这是Object类中定义的方法,这个方法返回的是当前类(实例)的类类型。
    rawType = getSuperclassTypeParameter(getClass());
  }

  Type getSuperclassTypeParameter(Class<?> clazz) {
	//得到泛型T的实际类型(class, interface, primitive type or void)
	//通过给定参数的getGenericSuperclass()方法来获取该类类型的上一级类型(直接超类,父类,即参数类类型继承的类的类型)并带有参数类型,即带泛型。如果要获取不带泛型的父类可使用getSuperclass()方法。
    Type genericSuperclass = clazz.getGenericSuperclass();
    //知识点:泛型类不是Class类的实例。因为泛型类是Java中一种独特的存在,它一般用于传递类,类似于一般方法中传递对象的概念,它不是简单的类,而是一种带有抽象概念性质的一种类,它会通过所传递的类(参数化类)来指定当前类所代表的的属于基本类型中的哪一类类型。(通过两种类型来确定具体的类型)
    if (genericSuperclass instanceof Class) {//判断获取的类型是否是Class类的实例,Class类是对Java中类的抽象,它本身也是一个类,但它是出于其他类上一层次的类,是类的顶层抽象
      // try to climb up the hierarchy until meet something useful
      //但是如果获取的genericSuperclass类型不带泛型,那么会执行下面代码,再次判断,获取的类型是否是TypeReference类型,如果不是该类型,则执行递归操作,
    	//如果是该类型,那么说明第一步通过getGenericSuperclass()获取带泛型的类型时未获取到泛型(因为MyBatis中TypeReference是泛型类),则说明程序出错,此处抛出类型异常,提示丢失泛型。
      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.");
    }
    //如果获取的genericSuperclass类型式带泛型的类型,则会执行下面代码,将该类型强转为参数化类型,使用其getActualTypeArguments()方法来获取其参数类型(泛型类型),
    //因为该方法获取的泛型类型可能不是一个,所以返回的是一个数组,但是我们这里只会获取到一个,所以取第一个即可。
    Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
    // TODO remove this when Reflector is fixed to return Types
    //对获取的参数类型进行判断如果该类型还是参数化类型(仍然带有泛型,即泛型嵌套的模式),那么就需要再次执行getActualTypeArguments()方法来获取其泛型类型(参数类型),
    //最后将该类型返回(赋值给字段)
    //为什么只会获取两次呢?因为,通过之前的类架构我们已经明白,具体的类型处理器最多只会存在两层继承。
    if (rawType instanceof ParameterizedType) {
      rawType = ((ParameterizedType) rawType).getRawType();
    }
    return rawType;
  }
  public final Type getRawType() {
    return rawType;
  }
  @Override
  public String toString() {
    return rawType.toString();
  }
}

3、BaseTypeHandler类

  BaseTypeHandler继承了TypeReference抽象类,实现了TypeHandler接口,它本身仍然是抽象类,在它内部简单的实现了TypeHandler接口中定义的四个方法中在所有类型处理器都需要考虑的公共代码。

  • 字段
  protected Configuration configuration;

  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }
  • setParameter()方法
      该方法主要实现了在SQL语句执行时,参数转换成jdbc对应的类型。该方法处理了当参数为空的情况,非空的情况交由setNonNullParameter()方法实现,该方法是抽象方法,需要在实现类中实现。
 @Override
  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 {
        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 abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  • getResult()方法及重载方法
      该方法主要实现了在SQL语句执行后,结果集转换成JAVA对应的类型。该方法通过getNullableResult()方法真正实现结果集的转换,该方法是抽象方法,需要在子类中实现。当结果集为null时,直接返回了null。剩下的两个重载方法,逻辑一样,不再贴代码。
@Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    T result;
    try {
      result = getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
    }
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
4、IntegerTypeHandler类

  以IntegerTypeHandler为例,分析类型处理器的具体实现类。所有的默认的类型处理器实现类,都通过继承抽象类BaseTypeHandler而实现。主要实现了在抽象类BaseTypeHandler中新定义的四个真正实现类型转换的四个方法。这些方法方式过程是,根据需要处理类型的不同,调用了jdbc中对应的具体方法方法而实现。具体代码如下:

public class IntegerTypeHandler extends BaseTypeHandler<Integer> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setInt(i, parameter);
  }

  @Override
  public Integer getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getInt(columnName);
  }

  @Override
  public Integer getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getInt(columnIndex);
  }

  @Override
  public Integer getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getInt(columnIndex);
  }
}
5、UnknownTypeHandler类

  UnknownTypeHandler是一个特殊的类型处理器,它也继承了BaseTypeHandler抽象类,并实现了对应的抽象方法。该类特殊的地方在于,它是一个未知的类型处理器,其中定义了一个默认的类型处理器ObjectTypeHandler,在进行类型转换的时候,根据jdbc或java类型判断所需要的类型处理器,如果无法判断则使用默认的类型处理器ObjectTypeHandler。

  • 字段和构造函数

  OBJECT_TYPE_HANDLER是默认的类型处理器,对应的类型处理器是ObjectTypeHandler;typeHandlerRegistry对应的事类型注册器,其中是系统中所有已经定义的类型注册器,并在构造函数中进行初始化。后续,会根据typeHandlerRegistry中的方法进行选择对应的类型处理器。

private static final ObjectTypeHandler OBJECT_TYPE_HANDLER = new ObjectTypeHandler();

  private TypeHandlerRegistry typeHandlerRegistry;

  public UnknownTypeHandler(TypeHandlerRegistry typeHandlerRegistry) {
    this.typeHandlerRegistry = typeHandlerRegistry;
  }
  • setNonNullParameter()方法
      抽象类BaseTypeHandler中定义的抽象方法的实现方法,实现参数类型转换。和普通的类型处理器相比,增加了类型处理器解析的代码。首先根据参数parameter、和jdbcType等参数,判断所需要的类型处理器handler,然后使用该类型处理器进行参数转换。如下所示:
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
      throws SQLException {
    TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
    handler.setParameter(ps, i, parameter, jdbcType);
  }

  resolveTypeHandler()方法主要实现了类型处理器的解析,根据参数parameter、和jdbcType等参数,判断所需要的类型处理器handler。如果参数parameter为null,直接使用OBJECT_TYPE_HANDLER类型处理器,否则根据typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType)方法获取对应的处理器,如果结果为null,还是使用OBJECT_TYPE_HANDLER类型处理器。

 private TypeHandler<? extends Object> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
    TypeHandler<? extends Object> handler;
    if (parameter == null) {
      handler = OBJECT_TYPE_HANDLER;
    } else {
      handler = typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType);
      // check if handler is null (issue #270)
      if (handler == null || handler instanceof UnknownTypeHandler) {
        handler = OBJECT_TYPE_HANDLER;
      }
    }
    return handler;
  }
  • getNullableResult()方法及其重载方法
      该方法及其重载方法中,也是首先根据参数判断所需要的参数处理器,然后再交由对应的类型处理器进行处理。代码如下所示:
@Override
  public Object getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    TypeHandler<?> handler = resolveTypeHandler(rs, columnName);
    return handler.getResult(rs, columnName);
  }

  resolveTypeHandler(ResultSet rs, String column)方法主要是根据对应信息获取对应的类型处理器,该方法主要是实现了根据column名称,转换成对应的columnIndex,然后通过resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex)方法实现具体的解析。

private TypeHandler<?> resolveTypeHandler(ResultSet rs, String column) {
    try {
      Map<String,Integer> columnIndexLookup;
      columnIndexLookup = new HashMap<String,Integer>();
      ResultSetMetaData rsmd = rs.getMetaData();
      int count = rsmd.getColumnCount();
      for (int i=1; i <= count; i++) {
        String name = rsmd.getColumnName(i);
        columnIndexLookup.put(name,i);
      }
      Integer columnIndex = columnIndexLookup.get(column);
      TypeHandler<?> handler = null;
      if (columnIndex != null) {
        handler = resolveTypeHandler(rsmd, columnIndex);
      }
      if (handler == null || handler instanceof UnknownTypeHandler) {
        handler = OBJECT_TYPE_HANDLER;
      }
      return handler;
    } catch (SQLException e) {
      throw new TypeException("Error determining JDBC type for column " + column + ".  Cause: " + e, e);
    }
  }

  resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex)方法主要是根据对应信息获取对应的类型处理器。其中首先根据参数信息获取对应的jdbcType和javaType类型,然后根据jdbcType和javaType参数,调用typeHandlerRegistry.getTypeHandler()方法,获取对应的类型处理器。

private TypeHandler<?> resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex) throws SQLException {
    TypeHandler<?> handler = null;
    JdbcType jdbcType = safeGetJdbcTypeForColumn(rsmd, columnIndex);
    Class<?> javaType = safeGetClassForColumn(rsmd, columnIndex);
    if (javaType != null && jdbcType != null) {
      handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
    } else if (javaType != null) {
      handler = typeHandlerRegistry.getTypeHandler(javaType);
    } else if (jdbcType != null) {
      handler = typeHandlerRegistry.getTypeHandler(jdbcType);
    }
    return handler;
  }

  private JdbcType safeGetJdbcTypeForColumn(ResultSetMetaData rsmd, Integer columnIndex) {
    try {
      return JdbcType.forCode(rsmd.getColumnType(columnIndex));
    } catch (Exception e) {
      return null;
    }
  }

  private Class<?> safeGetClassForColumn(ResultSetMetaData rsmd, Integer columnIndex) {
    try {
      return Resources.classForName(rsmd.getColumnClassName(columnIndex));
    } catch (Exception e) {
      return null;
    }
  }
发布了48 篇原创文章 · 获赞 3 · 访问量 3144

猜你喜欢

转载自blog.csdn.net/hou_ge/article/details/100880149