mybatis源码解析之 TypeHandler

TypeHanler

MyBatis 在预处理语句(PreparedStatement)中设置一个参数或者从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。Mybatis默认为我们实现了许多TypeHandler, 当我们没有配置指定TypeHandler时,Mybatis会根据参数或者返回结果的不同,默认为我们选择合适的TypeHandler处理。

在MyBatis中,StatementHandler负责对需要执行的SQL语句进行预编译处理,主要完成以下两项工作:
1.调用参数处理器(ParameterHandler)来设置需要传入SQL的参数;
2.调用结果集处理器(ResultSetHandler)来处理查询到的结果数据;

而不管要完成其中的哪一项工作都需要使用类型处理器(TypeHandler)来进行数据类型处理,无论是ParameterHandler为预处理语句(PreparedStatement)设置一个参数,还是ResultSetHandler将结果集中的一条记录转换为合适的Java类型。在整个过程中,TypeHandler负责完成数据库类型与JavaBean类型的转换工作。

配置示例


<typeHandlers>
    
	<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>

	<!-- javaType 配置java类型,例如String, 如果配上javaType, 那么指定的typeHandler就只作用于指定的类型 -->
	<typeHandler handler="" javaType=""/>
    
	<!-- jdbcType 配置数据库基本数据类型,例如varchar, 如果配上jdbcType, 那么指定的typeHandler就只作用于指定的类型  -->
	<typeHandler jdbcType="" handler=""/>
    
	<!-- 也可两者都配置 -->
    <typeHandler javaType="" jdbcType="" handler=""/>
    
    <!-- 扫描包的方式 -->
	<package name="org.mybatis.example"/>
</typeHandlers>

实现原理

Map<Class<?>, TypeHandler< ? >> ALL_TYPE_HANDLERS_MAP:所有 TypeHandler 的类名和类的映射关系
Map<Type, Map<JdbcType, TypeHandler < ? >>>  TYPE_HANDLER_MAP:所有 Type 管理的映射关系
Map<JdbcType, TypeHandler< ? >> JDBC_TYPE_HANDLER_MAP:

配置解析

  private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //如果是 package,找到 name 对应的包名。扫描包下所有非接口、非匿名、非抽象类。
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          //当 javaTypeName 为类的全路径或者是已经注册的类的别名的时候不为空
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          //jdbcTypeName 必须为 JdbcType 支持的类型
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          //当 handlerTypeName 为类的全路径或者是已经注册的类的别名的时候不为空
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              //
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            //扫描 typeHandlerClass 的 MapperType 注解
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

!mybatis源码解析之 TypeHandler

查找某个类的 TypeHandler

在实际配置文件中,会根据 jdbcType 和 javaType 找对应的 TypeHandler,查询过程如下:

  1. 如果不是 Enum 类型:从当前类及其父类依次向上查找,知道找到对应的 TypeHandler
  2. 如果是 Enum 类型:采用深度优先的算法从 Enum 的接口中查找。
  private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    if (ParamMap.class.equals(type)) {
      return null;
    }
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
      handler = jdbcHandlerMap.get(jdbcType);
      if (handler == null) {
        handler = jdbcHandlerMap.get(null);
      }
      if (handler == null) {
        // #591
        handler = pickSoleHandler(jdbcHandlerMap);
      }
    }
    // type drives generics here
    return (TypeHandler<T>) handler;
  }

  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
    Map<JdbcType, TypeHandler<?>> 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;
      //Enum 类型特殊处理
      if (clazz.isEnum()) {
        jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(clazz, clazz);
        if (jdbcHandlerMap == null) {
          register(clazz, getInstance(clazz, defaultEnumTypeHandler));
          return TYPE_HANDLER_MAP.get(clazz);
        }
      } else { //非 Enum 类型,会从父类一直查找。
        jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
      }
    }
    TYPE_HANDLER_MAP.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
    return jdbcHandlerMap;
  }

  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForEnumInterfaces(Class<?> clazz, Class<?> enumClazz) {
    for (Class<?> iface : clazz.getInterfaces()) {
      Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(iface);
      if (jdbcHandlerMap == null) {
        //深度优先遍历查找
        jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(iface, enumClazz);
      }
      if (jdbcHandlerMap != null) {
        // Found a type handler regsiterd to a super interface
        HashMap<JdbcType, TypeHandler<?>> newMap = new HashMap<>();
        for (Entry<JdbcType, TypeHandler<?>> entry : jdbcHandlerMap.entrySet()) {
          // Create a type handler instance with enum type as a constructor arg
          newMap.put(entry.getKey(), getInstance(enumClazz, entry.getValue().getClass()));
        }
        return newMap;
      }
    }
    return null;
  }

  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
    Class<?> superclass =  clazz.getSuperclass();
    if (superclass == null || Object.class.equals(superclass)) {
      return null;
    }
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(superclass);
    if (jdbcHandlerMap != null) {
      return jdbcHandlerMap;
    } else {
      //递归查找
      return getJdbcHandlerMapForSuperclass(superclass);
    }
  }

  private TypeHandler<?> pickSoleHandler(Map<JdbcType, TypeHandler<?>> jdbcHandlerMap) {
    TypeHandler<?> soleHandler = null;
    for (TypeHandler<?> handler : jdbcHandlerMap.values()) {
      if (soleHandler == null) {
        soleHandler = handler;
      } else if (!handler.getClass().equals(soleHandler.getClass())) {
        // More than one type handlers registered.
        return null;
      }
    }
    return soleHandler;
  }

备注:

  1. typeHandler标签支持配置文件和注解两种方式配置 JavaType 和 JdbcType。配置文件的优先级高于注解的优先级。推荐用注解的方式
  2. package 标签只支持注解的方式配置javaType 和 JdbcType。
  3. 在查找 Type 的时候,如果当前没有对应的 Map<JdbcType, TypeHandler<?>>,会继续从父类中找,直到 Object 向上找。
  4. 如果 Type 为 Enum 接口,采用深度优先遍历查找。非 Enum 类型,采用递归向上直到 Object。
  5. @MappedTypes 或 @MappedJdbcTypes 的值为数组。因此,支持多个类型的。问题:什么时候可以给 MapperTypes 和 MappedJdbcTypes 配置多个值?

自定义 TypeHandler

(1)创建一个继承自 BaseType 的自定义typeHandler的类;

(2)在Mybatis配置文件或注解中中配置类型处理器

(3)在引射器的XML配置中标识需要用自定义typeHandler处理的参数或者结果。

在自定义 TypeHandler 的时候,简单得继承 BaseTypeHandler 即可。

TypeHandler

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  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 都必须实现接口 TypeHandler

BaseTypeHandler

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
    
  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;

}

mybatis 默认的 BaseTypeHandler 统一了异常处理,实现者自定义 TypHanlder 应该继承BaseTypeHandler 而不是TypeHandler

StringTypeHandler


@MappedJdbcType(String.class)
public class StringTypeHandler extends BaseTypeHandler<String> {

  //预处理的的时候,将 Java 类型 parameter 转换为 JDBC 类型
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setString(i, parameter);
  }
   
  //将 JDBC 类型转换为 Java 类型
  @Override
  public String getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getString(columnName);
  }

  //将 JDBC 类型转换为 Java 类型
  @Override
  public String getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getString(columnIndex);
  }

  //将 JDBC 类型转换为 Java 类型
  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getString(columnIndex);
  }
}

这里 Jdbc 类型为 VARCHAR,Java 类型为 String。

问题:如何自定义 TypeHandler 将 Enum 类转换为 int 类型。 答案参考 EnumOrdinalTypeHandler

<resultMap type="cn.don.pojo.Student" id="queryStudentByNumResult">
	<result property="name" column="name" typeHandler="StringTypeHandler"/>
</resultMap>

参考

简单的例子:https://blog.csdn.net/u012525096/article/details/82459455

这篇文章的例子不错:https://elim.iteye.com/blog/1847854

附录

    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());

    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());

    register(Reader.class, new ClobReaderTypeHandler());
    register(String.class, new StringTypeHandler());
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());
    register(JdbcType.CLOB, new ClobTypeHandler());
    register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
    register(JdbcType.NCHAR, new NStringTypeHandler());
    register(JdbcType.NCLOB, new NClobTypeHandler());

    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
    register(JdbcType.ARRAY, new ArrayTypeHandler());

    register(BigInteger.class, new BigIntegerTypeHandler());
    register(JdbcType.BIGINT, new LongTypeHandler());

    register(BigDecimal.class, new BigDecimalTypeHandler());
    register(JdbcType.REAL, new BigDecimalTypeHandler());
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

    register(InputStream.class, new BlobInputStreamTypeHandler());
    register(Byte[].class, new ByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.BLOB, new BlobTypeHandler());

    register(Object.class, UNKNOWN_TYPE_HANDLER);
    register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);

    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
    register(JdbcType.DATE, new DateOnlyTypeHandler());
    register(JdbcType.TIME, new TimeOnlyTypeHandler());

    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

    register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());

    register(Instant.class, InstantTypeHandler.class);
    register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
    register(LocalDate.class, LocalDateTypeHandler.class);
    register(LocalTime.class, LocalTimeTypeHandler.class);
    register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
    register(OffsetTime.class, OffsetTimeTypeHandler.class);
    register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
    register(Month.class, MonthTypeHandler.class);
    register(Year.class, YearTypeHandler.class);
    register(YearMonth.class, YearMonthTypeHandler.class);
    register(JapaneseDate.class, JapaneseDateTypeHandler.class);

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());

已经实现但是默认没有注册的handler

  • ArrayTypeHandler

  • EnumOrdinalTypeHandler

  • EnumTypeHandler

  • ObjectTypeHandler