我们知道 JDBC 的数据类型与 Java 语言中的数据类型虽然有对应关系,但并不能做到一一对应,因此也就无法做到自动映射。以下是几个例子:

SQL 类型Java 类型
VARCHARjava.lang.String
CHARjava.lang.String
BLOBjava.lang.byte[]
INTEGER UNSIGNEDjava.lang.Long
TINYINT UNSIGNEDjava.lang.Integer
SMALLINT UNSIGNEDjava.lang.Integer
MEDIUMINT UNSIGNEDjava.lang.Integer
BITjava.lang.Boolean
BITINT UNSIGNEDjava.math.BigInteger
FLOATjava.lang.Float
DOUBLEjava.lang.Double
DECIMALjava.math.BigDecimal

在使用 PreparedStatement 执行 SQL 语句之前,需要手动调用 setInt()setString() 等 set 方法绑定参数,这不仅仅是告诉 JDBC 一个 SQL 模板中哪个占位符需要使用哪个实参,还会将数据从 Java 类型转换成 JDBC 类型。当从 ResultSet 中获取数据的时候,则是一个逆过程,数据会从 JDBC 类型转换为 Java 类型。

MyBatis 使用类型转换模块完成上述类型的转换,具体代码实现位于 org.apache.ibatis.type 包中,接下来我们深入分析该模块的核心实现。

type.TypeHandler

TypeHandler 接口是 MyBatis 类型转换的核心,其定义如下:

  public interface TypeHandler<T> {
  // 在通过 PreparedStatement 为 SQL 语句绑定参数时,会将传入的实参数据由 JdbcType 类型转换成 Java 类型
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  // 从 ResultSet 中获取数据时会使用 getResult() 方法,其中会将读取到的数据由 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;
}
  

type.BaseTypeHandler

MyBatis 中定义了一个 BaseTypeHandler 抽象类来实现一些 TypeHandler 的公共逻辑,BaseTypeHandler 在实现 TypeHandler 的同时,还实现了 TypeReference 抽象类。其继承关系如下图所示:

TypeHandler 继承关系图

BaseTypeHandler 中,简单实现了 TypeHandler 接口的 setParameter() 方法和 getResult() 方法,其中:

  • setParameter() 实现中,会判断传入的 parameter 实参是否为空:
    • 如果为空,则调用 PreparedStatement.setNull() 方法进行设置;
    • 如果不为空,则委托 setNonNullParameter() 这个抽象方法进行处理,setNonNullParameter() 方法由 BaseTypeHandler 的子类提供具体实现。
  • getResult() 的三个重载实现中,会直接调用相应的 getNullableResult() 抽象方法,这里有三个重载的 getNullableResult() 抽象方法,它们都由 BaseTypeHandler 的子类提供具体实现。

MyBatis 源码中有许多 BaseTypeHandler 的实现类,他们的实现都大同小异,这里我们以 LongTypeHandler 为例进行分析:

  @Override
public void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType)
    throws SQLException {
  // 调用 PreparedStatement.setLong() 实现参数绑定
  ps.setLong(i, parameter);
}
@Override
public Long getNullableResult(ResultSet rs, String columnName)
    throws SQLException {
  // 调用 ResultSet.getLong() 获取指定列值
  long result = rs.getLong(columnName);
  return result == 0 && rs.wasNull() ? null : result;
}
  

由上可知,LongTypeHandler 的核心还是通过 PreparedStatement.setLong() 方法以及 ResultSet.getLong() 方法实现的。至于其他 BaseTypeHandler 实现类的核心实现,同样也是依赖了 JDBC 的 API,这里就不再展开介绍了。

TypeHandler 注册过程

介绍完 TypeHandler 接口实现类的核心原理后,我们再来解决以下两个问题:

  • MyBatis 如何管理众多的 TypeHandler 接口实现?
  • 如何在合适的场景中使用合适的 TypeHandler 实现进行类型转换?

对于第一个问题,MyBatis 是通过在核心配置文件或者映射配置文件中指定 TypeHandler 属性的。无论是哪种配置方式,MyBatis 都会在初始化过程中,获取所有已知的 TypeHandler(包括内置实现和自定义实现),然后创建所有 TypeHandler 实例并注册到 TypeHandlerRegistry 中,由 TypeHandlerRegistry 统一管理所有 TypeHandler 实例。

TypeHandlerRegistry 在管理 TypeHandler 的时候,用到了以下四个最核心的集合:

  Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
  
  • jdbcTypeHandlerMapMap<JdbcType, TypeHandler<?>> 类型):该集合记录了 JdbcTypeTypeHandler 之间的关联关系。

    JdbcType 是一个枚举类型,每个 JdbcType 枚举值对应一种 JDBC 类型,例如,JdbcType.VARCHAR 对应的就是 JDBC 中的 varchar 类型。

    在从 ResultSet 中读取数据的时候,就会从 JDBC_TYPE_HANDLER_MAP 集合中根据 JDBC 类型查找对应的 TypeHandler,将数据转换成 Java 类型。

  • typeHandlerMapMap<Type, Map<JdbcType, TypeHandler<?>>> 类型):该集合第一层 Key 是需要转换的 Java 类型,第二层 Key 是转换的目标 JdbcType,最终的 Value 是完成此次转换时所需要使用的 TypeHandler 对象。

    为什么要设计两层 Map 呢?这里我们举个例子:Java 类型中的 String 可能转换成数据库中的 varchar、char、text 等多种类型,存在一对多的关系,所以就可能有不同的 TypeHandler 实现。

  • allTypeHandlersMapMap<Class, TypeHandler> 类型):该集合记录了全部 TypeHandler 的类型以及对应的 TypeHandler 实例对象。

  • NULL_TYPE_HANDLER_MAPMap<JdbcType, TypeHandler<?>> 类型):空 TypeHandler 集合的标识,默认值为 Collections.emptyMap()

MyBatis 在初始化的时候,会实例化全部 TypeHandler 对象,并调用 TypeHandlerRegistryregister() 完成这些 TypeHandler 对象的注册,具体实现在 TypeHandlerRegistry 的构造方法中:

  public TypeHandlerRegistry(Configuration configuration) {
  this.unknownTypeHandler = new UnknownTypeHandler(configuration);

  register(Boolean.class, new BooleanTypeHandler());
  register(boolean.class, new BooleanTypeHandler());
  register(JdbcType.BOOLEAN, new BooleanTypeHandler());
  // 省略 ...
}
  

这个注册过程的核心逻辑就是向上述四个核心集合中添加 TypeHandler 实例以及与 Java 类型、JDBC 类型之间的映射:

  // 如果是 jdbcType,直接添加映射到 jdbcTypeHandlerMap
public void register(JdbcType jdbcType, TypeHandler<?> handler) {
    jdbcTypeHandlerMap.put(jdbcType, handler);
}
  
  // 如果是 javaType,则调用重载方法
public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
  register((Type) javaType, typeHandler);
}
//配置该 TypeHandler 实现类能够处理的 JDBC 类型集合
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
  // 尝试从 TypeHandler 类中获取 @MappedJdbcTypes 注解
  MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
  if (mappedJdbcTypes != null) {
    // 根据 @MappedJdbcTypes 注解指定的 JDBC 类型进行注册
    for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
      // 交给下面的三参数重载处理
      register(javaType, handledJdbcType, typeHandler);
    }
    // 如果支持 jdbcType 为 null,也是交给下面的三参数重载处理
    if (mappedJdbcTypes.includeNullJdbcType()) {
      register(javaType, null, typeHandler);
    }
  } else {
    // 如果没有配置 MappedJdbcTypes 注解,也是交给下面的三参数重载处理
    register(javaType, null, typeHandler);
  }
}
//映射 javaType 与 jdbcType 的关系,同时注册转换时使用的 TypeHandler
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
  if (javaType != null) {
    // 检测是否明确指定了 TypeHandler 能够处理的 Java 类型
    // 根据指定的 Java 类型,从 typeHandlerMap 集合中获取相应的 TypeHandler 集合
    Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
    if (map == null || map == NULL_TYPE_HANDLER_MAP) {
      map = new HashMap<>();
    }
    // 将 TypeHandler 实例记录到 typeHandlerMap 集合
    map.put(jdbcType, handler);
    typeHandlerMap.put(javaType, map);
  }
  // 向 allTypeHandlersMap 集合注册 TypeHandler 类型和对应的 TypeHandler 对象
  allTypeHandlersMap.put(handler.getClass(), handler);
}
  

除了以上在构造方法中用到的 register(),还有两个比较重要的重载方法,一个是读取 @MappedTypes 注解的 register() 方法重载:

  public void register(Class<?> typeHandlerClass) {
  Boolean mappedTypeFound = false;
  // 读取 TypeHandler 类中定义的@MappedTypes 注解
  MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
  if (mappedTypes != null) {
    for (Class<?> javaTypeClass : mappedTypes.value()) {
      // 根据@MappedTypes 注解中指定的 Java 类型进行注册,最终调用的还是三参数重载方法
      register(javaTypeClass, typeHandlerClass);
      mappedTypeFound = true;
    }
  }
  if (!mappedTypeFound) {
    register(getInstance(null, typeHandlerClass));
  }
}
  

另一个是扫描一个包下的全部 TypeHandler 接口实现类的 register() 重载:

  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) {
    //忽略内部类和接口(包括 package-info.java)和抽象类
    if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
      // 调用的是前边的读取 `@MappedTypes` 注解的 `register()` 方法重载
      register(type);
    }
  }
}
  

TypeHandler 查询过程

分析完注册 TypeHandler 实例的注册流程后,我们再来看看 MyBatis 是如何从 TypeHandlerRegistry 底层的这几个集合中查找正确的 TypeHandler 实例的,该功能的具体实现是在 TypeHandlerRegistrygetTypeHandler() 方法中

这里的 getTypeHandler() 方法也有多个重载,最核心的重载是 getTypeHandler(Type,JdbcType),其中会根据传入的 Java 类型和 JDBC 类型,从底层的几个集合中查询相应的 TypeHandler 实例,具体实现如下:

  private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
  if (ParamMap.class.equals(type)) {
    // 过滤掉 ParamMap 类型
    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) {
      // 没有对应的 TypeHandler 实例,则使用 null 对应的 TypeHandler
      handler = jdbcHandlerMap.get(null);
    }
    if (handler == null) {
      // 如果 jdbcHandlerMap 只注册了一个 TypeHandler,则使用此 TypeHandler 对象
      handler = pickSoleHandler(jdbcHandlerMap);
    }
  }
  return (TypeHandler<T>) handler;
}
  

以上方法中会调用 getJdbcHandlerMap() 方法检测 typeHandlerMap 集合中相应的 TypeHandler 集合是否已经初始化。

  • 如果已初始化,则直接使用该集合进行查询;
  • 如果未初始化,则尝试以传入的 Java 类型的、已初始化的父类对应的 TypeHandler 集合作为初始集合;
  • 如果该 Java 类型的父类没有关联任何已初始化的 TypeHandler 集合,则将该 Java 类型对应的 TypeHandler 集合初始化为 NULL_TYPE_HANDLER_MAP 标识。

getJdbcHandlerMap() 方法具体实现如下:

  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
  // 首先查找指定 Java 类型对应的 TypeHandler 集合
  Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
  if (jdbcHandlerMap != null) {
    // 如果已经初始化,直接使用集合进行查询
    return NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap) ? null : jdbcHandlerMap;
  }
  // 如果没有初始化,则在这里初始化指定 Java 类型的 TypeHandler 集合
  if (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 集合,并将其作为 clazz 对应的 TypeHandler 集合
      jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
    }
  }
  // 如果上述查找皆失败,则以 NULL_TYPE_HANDLER_MAP 作为 clazz 对应的 TypeHandler 集合
  typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
  return jdbcHandlerMap;
}
  

这里调用的 getJdbcHandlerMapForSuperclass() 方法会判断传入的 clazz 的父类是否为空或 Object。如果是,则方法直接返回 null;如果不是,则尝试从 typeHandlerMap 集合中获取父类对应的 TypeHandler 集合,但如果父类没有关联 TypeHandler 集合,则递归调用 getJdbcHandlerMapForSuperclass() 方法顺着继承树继续向上查找父类,直到查找到父类的 TypeHandler 集合,然后直接返回。具体实现如下:

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

type.TypeAliasRegistry 别名管理

我们可以在核心配置文件中使用 <typeAlias> 标签为类的全限定名定义别名,后续编写 SQL 语句、定义 <resultMap> 的时候,直接使用这些别名即可完全替代相应的完整 Java 类名,这样就非常易于代码的编写和维护。

TypeAliasRegistry 是维护别名配置的核心实现所在,其中提供了别名注册、别名查询的基本功能。在 TypeAliasRegistrytypeAliases 字段(Map<String, Class<?>> 类型)中记录了别名与 Java 类型之间的对应关系,我们可以通过 registerAlias() 方法完成别名的注册,具体实现如下:

  public void registerAlias(String alias, Class<?> value) {
  if (alias == null) {
    // 传入的别名为 null,直接抛出异常
    throw new TypeException("The parameter alias cannot be null");
  }
  // 将别名全部转换为小写
  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 集合中记录别名与类之间的映射关系
  typeAliases.put(key, value);
}
  

TypeAliasRegistry 的构造方法中,会通过上述 registerAlias() 方法将 Java 的基本类型、基本类型的数组类型、基本类型的封装类、封装类型的数组类型、DateBigDecimalBigIntegerMapHashMapListArrayListCollectionIteratorResultSet 等常用类型添加了别名:

  public TypeAliasRegistry() {
    registerAlias("string", String.class);
    registerAlias("byte", Byte.class);
    registerAlias("char", Character.class);
    //...
}
  

除了明确传入别名与相应的 Java 类型之外,TypeAliasRegistry 还提供了扫描指定包名下所有的类中的 @Alias 注解获取别名配置,并完成注册的功能,这个功能涉及一个 registerAliases() 方法的重载,相关实现如下:

  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) {
    // 过滤掉内部类、接口以及抽象类
    if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
      // 扫描类中的@Alias 注解
      registerAlias(type);
    }
  }
}
public void registerAlias(Class<?> type) {
  // 获取类的简单名称,其中不会包含包名
  String alias = type.getSimpleName();
  // 获取类中的@Alias 注解
  Alias aliasAnnotation = type.getAnnotation(Alias.class);
  if (aliasAnnotation != null) {
    alias = aliasAnnotation.value();
  }
  // 这里的@Alias 注解指定的别名与 type 类型绑定
  registerAlias(alias, type);
}