MyBatis 对未知数据类型的转换(二)TypeHandlerRegistry

# 问题

上一篇我们讲到,对于未知数据类型的解析,UnknownTypeHandler 把部分任务交给了 TypeHandlerRegistry ,甚至可能仅仅只丢了一个 javaType 过来就要求返回一个合适的解析器回去 。TypeHandlerRegistry 到底里面做了啥可以找到合适的解析器呢?

# 功能

从类的名字我们就知道这个类的功能大概就是个注册表,而且很可能是全局共用的,记录各种 javaType,jdbcType,TypeHandler 的映射关系。实际这个类的核心也就是维护了几个 map 。

# 核心字段

# JDBC_TYPE_HANDLER_MAP

1
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);

这个很直观,就是注册 jdbcType 和解析器的对应关系。

# TYPE_HANDLER_MAP

1
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();

书上原文是:“记录了 java 类型向指定的 JdbcType 转换时,需要使用的 TypeHandler 对象。例如:Java 类型中的 String 转换成数据库的 char 、varchar 等多种类型,所以存在一对多关系”。

Type 是 java.lang.reflect 包下的接口,Class 类实现了此接口。所以此 map 的 key 值是 javaType ,比如 String.class 。

# UNKNOWN_TYPE_HANDLER

就是上一篇文章学习过的 UnknownTypeHandler 的实例,主要用在 Object.class 和 JdbcType.OTHER 上。

# ALL_TYPE_HANDLERS_MAP

1
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

key 是解析器的 class ,value 是解析器自身。

记录了全部的解析器的类型及该类型相对应的 TypeHandler 对象。

# NULL_TYPE_HANDLER_MAP

仅仅是一个空 TypeHandler 集合的标识。因为 TYPE_HANDLER_MAP 是ConcurrentHashMap, 不能塞 null 值,因此在需要的地方以此空标识作为 value 替代null塞入。

# 注册

在构造方法中,就调用了大量的 register(…) 的方法,注册了很多映射关系。

register 有很多重载方法,除了简单的向 JDBC_TYPE_HANDLER_MAP 注册之外,全都最终指向了下面的重载方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
  if (javaType != null) {
    Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
    if (map == null) {
      map = new HashMap<JdbcType, TypeHandler<?>>();
      TYPE_HANDLER_MAP.put(javaType, map);
    }
    map.put(jdbcType, handler);
  }
  ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}

方法并不复杂,我们只需要理解的是这里为两个 map 中都塞入了值,一个是 key 为 javaType 的 map , 也就是 String 与 char/varchar 的一对多的关系。另一个则是记录全部 handler 的map。

这里有一点需要特别留意的就是,比如 String 类,除了映射 char/varchar 之外,还映射 null ,对应的解析器也是 StringTypeHandler 。

1
2
3
4
//这一行就是javaType为String.class jdbcType 为null 的解析器注册
register(String.class, new StringTypeHandler());
//这是普通的注册
register(String.class, JdbcType.CHAR, new StringTypeHandler());

# 查找 TypeHandler

终于到了查找 TypeHandler 的部分了,看了这么久,是不是差点晕得都忘了我们的这个注册表最核心的功能在这呢?

根据 jdbcType 和 typeHandler 的class 查找解析器的功能都很简单,就是上面的 JDBC_TYPE_HANDLER_MAP 和 ALL_TYPE_HANDLERS_MAP 中取值。

关键的在于根据 javaType 寻找解析器。而且我们的在上一篇也讲到了 PreparedStatement 在赋值的时候,我们没有提供 jdbcType ( null ), 仅仅只有 javaType 。

getTypeHandler 其实就是从 TYPE_HANDLER_MAP 取值.我们入参类型为 String 为例来看看其查找解析器的过程 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
//首先根据 String.class 查找其一对多的解析器集合.
  Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
  TypeHandler<?> handler = null;
  if (jdbcHandlerMap != null) {
  //然后根据其指定的 jdbcType 来找对应的解析器。而此处我们的 jdbcType 是null
  //但是在注册的时候我们留意过,对于 null ,同样也进行了注册
  //就相当于是给了个默认的解析器
    handler = jdbcHandlerMap.get(jdbcType);
    if (handler == null) {
      handler = jdbcHandlerMap.get(null);
    }
    if (handler == null) {
      handler = pickSoleHandler(jdbcHandlerMap);
    }
  }
  return (TypeHandler<T>) handler;
}

# 总结

目前我们已经接触过了两种方式对明确指明的 javaType 的转换。主要可以分为两大类:

# ObjectTypeHandler

这个主要依靠 JDBC 底层的方法来查找合适的 javaType ,大量的 switch-case 语句。主要用在将返回值的封装到 JSONObject 中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
switch (field.getSQLType()) {
case Types.TINYINT:
    if (!field.isUnsigned()) {
        return Integer.valueOf(getByte(columnIndex));
    }
    return Integer.valueOf(getInt(columnIndex));
case Types.SMALLINT:
    return Integer.valueOf(getInt(columnIndex));
    //...
}

# TypeHandlerRegistry

注册表功能,将常规的数十种关系映射在初始化时就都注册好,也就是提前存入 map 中,需要的时候去 map 中取。

尤其是将 javaType 和 TypeHandler 关联起来,并对一些类型注册了默认的解析器,即 jdbcType 未指明时所要采用的解析器。

这个更多是用在 PreparedStatement 入参的赋值时。