*声明,本文章的参考
Mybatis 3.5.19 源码
技术文章摘抄-深入剖析 MyBatis 核心原理-完
通过反射包提供的工具,我们可以为 JDBC 对象和 Java 对象完成转换和赋值操作,但是还有一个问题,数据库中的数据类型和 Java 数据类型不是一一对应的,比如 VARCHAR 和 CHAR 都是对应了 String 类型,那么 MyBatis 在进行数据转换时,该如何转换为相对应的类型呢。

TypeHandler
MyBatis 巧妙的使用了一个模版方法模式,先看一看接口和抽象方法:
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;
}
public abstract class BaseTypeHandler<T> implements TypeHandler<T> {
@Deprecated
protected Configuration configuration;
@Deprecated
public void setConfiguration(Configuration c) {
this.configuration = c;
}
@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);
}
}
}
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
try {
return getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e,
e);
}
}
@Override
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
try {
return getNullableResult(rs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set. Cause: " + e,
e);
}
}
@Override
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
try {
return getNullableResult(cs, columnIndex);
} catch (Exception e) {
throw new ResultMapException(
"Error attempting to get column #" + columnIndex + " from callable statement. Cause: " + e, e);
}
}
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;
}Java可以看到 BashTypeHandler 的实现方法中调用了 setNonNullParameter 、getNullableResult 方法,但是并没有实现逻辑,只保留了抽象接口。然后具体的实现类只需要重写这三个接口即可。

BaseTypeHandler 的实现类有很多,上面没有全部展示,以其中的一个 LongTypeHandler 方法来看眼实现:
public class LongTypeHandler extends BaseTypeHandler<Long> {
// 创建一个全局的单例
public static final LongTypeHandler INSTANCE = new LongTypeHandler();
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {
ps.setLong(i, parameter);
}
@Override
public Long getNullableResult(ResultSet rs, String columnName) throws SQLException {
long result = rs.getLong(columnName);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Long getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
long result = rs.getLong(columnIndex);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Long getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
long result = cs.getLong(columnIndex);
return result == 0 && cs.wasNull() ? null : result;
}
}
Java可以看到,实际做的就是帮我们将数据从 JDBC 的类型转换为 Java 的类型。这样就解决了转换问题,但是有一个新的问题,该如何找到我们需要的那个转换器。这时就需要一个仓库 TypehandlerRegistry。
TypehandlerRegistry
管理 Handler 的仓库,注册在 configuration 中。

其中有四个非常重要的集合:
- jdbcTypeHandlerMap:该集合记录了 JdbcType 与 TypeHandler 之间的关联关系。JdbcType 是一个枚举类型,每个 JdbcType 枚举值对应一种 JDBC 类型,例如,JdbcType.VARCHAR 对应的就是 JDBC 中的 varchar 类型。在从 ResultSet 中读取数据的时候,就会从 JDBC_TYPE_HANDLER_MAP 集合中根据 JDBC 类型查找对应的 TypeHandler,将数据转换成 Java 类型。
- typeHandlerMap:该集合第一层 Key 是需要转换的 Java 类型,第二层 Key 是转换的目标 JdbcType,最终的 Value 是完成此次转换时所需要使用的 TypeHandler 对象。那为什么要有两层 Map 的设计呢?这里我们举个例子:Java 类型中的 String 可能转换成数据库中的 varchar、char、text 等多种类型,存在一对多关系,所以就可能有不同的 TypeHandler 实现。
- allTypeHandlersMap:该集合记录了全部 TypeHandler 的类型以及对应的 TypeHandler 实例对象。
- NULL_TYPE_HANDLER_MAP:空 TypeHandler 集合的标识,默认值为 Collections.emptyMap()。
项目启动时,configuration 会触发他的构造器的,将所有的常见的组合注册进去:
public TypeHandlerRegistry(Configuration configuration) {
// If a handler is registered against null JDBC type, it is the default handler for the Java type. Users can
// override the default handler (e.g. `register(boolean.class, null, new YNBooleanTypeHandler())` or register a
// custom handler for a specific Java-JDBC type combination (e.g. `register(boolean.class, JdbcType.CHAR, new
// YNBooleanTypeHandler())`).
register(new Type[] { Boolean.class, boolean.class }, new JdbcType[] { null }, BooleanTypeHandler.INSTANCE);
register(new Type[] { Byte.class, byte.class }, new JdbcType[] { null }, ByteTypeHandler.INSTANCE);
register(new Type[] { Short.class, short.class }, new JdbcType[] { null }, ShortTypeHandler.INSTANCE);
register(new Type[] { Integer.class, int.class }, new JdbcType[] { null }, IntegerTypeHandler.INSTANCE);
register(new Type[] { Long.class, long.class }, new JdbcType[] { null }, LongTypeHandler.INSTANCE);
register(new Type[] { Float.class, float.class }, new JdbcType[] { null }, FloatTypeHandler.INSTANCE);
register(new Type[] { Double.class, double.class }, new JdbcType[] { null }, DoubleTypeHandler.INSTANCE);
register(new Type[] { Character.class, char.class }, new JdbcType[] { null }, new CharacterTypeHandler());
register(String.class, null, StringTypeHandler.INSTANCE);
register(Reader.class, null, new ClobReaderTypeHandler());
register(BigInteger.class, null, new BigIntegerTypeHandler());
register(BigDecimal.class, null, BigDecimalTypeHandler.INSTANCE);
register(InputStream.class, null, new BlobInputStreamTypeHandler());
register(Byte[].class, null, new ByteObjectArrayTypeHandler());
register(byte[].class, null, ByteArrayTypeHandler.INSTANCE);
register(Date.class, null, DateTypeHandler.INSTANCE);
register(java.sql.Date.class, null, new SqlDateTypeHandler());
// .........
jdbcTypeHandlerMap.put(JdbcType.NCHAR, NStringTypeHandler.INSTANCE);
jdbcTypeHandlerMap.put(JdbcType.LONGNVARCHAR, NStringTypeHandler.INSTANCE);
jdbcTypeHandlerMap.put(JdbcType.NCLOB, NClobTypeHandler.INSTANCE);
jdbcTypeHandlerMap.put(JdbcType.ARRAY, new ArrayTypeHandler());
jdbcTypeHandlerMap.put(JdbcType.BINARY, ByteArrayTypeHandler.INSTANCE);
jdbcTypeHandlerMap.put(JdbcType.VARBINARY, ByteArrayTypeHandler.INSTANCE);
jdbcTypeHandlerMap.put(JdbcType.LONGVARBINARY, ByteArrayTypeHandler.INSTANCE);
jdbcTypeHandlerMap.put(JdbcType.BLOB, BlobTypeHandler.INSTANCE);
jdbcTypeHandlerMap.put(JdbcType.TIMESTAMP, DateTypeHandler.INSTANCE);
jdbcTypeHandlerMap.put(JdbcType.DATE, DateOnlyTypeHandler.INSTANCE);
jdbcTypeHandlerMap.put(JdbcType.TIME, TimeOnlyTypeHandler.INSTANCE);
}
Java下面就以 register(new Type[] { Long.class, long.class }, new JdbcType[] { null }, LongTypeHandler.INSTANCE); 为例,过一遍 register 的核心逻辑:
private void register(Type[] mappedJavaTypes, JdbcType[] mappedJdbcTypes, TypeHandler<?> handler) {
for (Type javaType : mappedJavaTypes) {
// 遍历需要注册的 java 类型,这里只要 Long 一个
if (javaType == null) {
continue;
}
// 如果已经创建了内层的 map ,则直接往里添加新的并修改;如果没有创建就创建并新增。
typeHandlerMap.compute(javaType, (k, v) -> {
Map<JdbcType, TypeHandler<?>> map = (v == null || v == NULL_TYPE_HANDLER_MAP ? new HashMap<>() : v);
// 从一步传过来的 mappedJdbcTypes 是 new JdbcType[] { null },也就是说他里面只要一项,就是个 null。这里我们要记得,null 可以作为 map 的 key ,且是唯一的。
for (JdbcType jdbcType : mappedJdbcTypes) {
map.put(jdbcType, handler);
}
return map;
});
// 处理含参数类型的数据类型,这里不会走到这
if (javaType instanceof ParameterizedType) {
// MEMO: add annotation to skip this?
Type rawType = ((ParameterizedType) javaType).getRawType();
typeHandlerMap.compute(rawType, (k, v) -> {
Map<JdbcType, TypeHandler<?>> map = (v == null || v == NULL_TYPE_HANDLER_MAP ? new HashMap<>() : v);
for (JdbcType jdbcType : mappedJdbcTypes) {
map.merge(jdbcType, handler, (handler1, handler2) -> handler1.equals(handler2) ? handler1
: new ConflictedTypeHandler((Class<?>) rawType, jdbcType, handler1, handler2));
}
return map;
});
}
}
allTypeHandlersMap.put(handler.getClass(), handler);
}Java这样就完成了所有常见类型的注册。同时,MyBatis 还提供了自定义式的 Handler 注册,当我们调用 register(TypeHandler<T> handler) 方法,会先分别通过 mappedJavaTypes 和 mappedJdbcTypes 两个方法分别读取 MappedTypes 和 MappedJdbcTypes 两个注解的内容,然后再走统一的 register方法:
public <T> void register(TypeHandler<T> handler) {
register(mappedJavaTypes(handler.getClass()), mappedJdbcTypes(handler.getClass()), handler);
}
private Type[] mappedJavaTypes(Class<?> clazz) {
MappedTypes mappedTypesAnno = clazz.getAnnotation(MappedTypes.class);
if (mappedTypesAnno != null) {
return mappedTypesAnno.value();
}
return TypeParameterResolver.resolveClassTypeParams(TypeHandler.class, clazz);
}
private JdbcType[] mappedJdbcTypes(Class<?> clazz) {
MappedJdbcTypes mappedJdbcTypesAnno = clazz.getAnnotation(MappedJdbcTypes.class);
if (mappedJdbcTypesAnno != null) {
JdbcType[] jdbcTypes = mappedJdbcTypesAnno.value();
if (mappedJdbcTypesAnno.includeNullJdbcType()) {
int newLength = jdbcTypes.length + 1;
jdbcTypes = Arrays.copyOf(jdbcTypes, newLength);
jdbcTypes[newLength - 1] = null;
}
return jdbcTypes;
}
return new JdbcType[] { null };
}
JavaTypehandlerRegister 提供了一系列重载的 getTypeHandler 方法用于获取 Handler ,其中最主要的一个实现是TypeHandler<?> getTypeHandler(Type type, JdbcType jdbcType)
public TypeHandler<?> getTypeHandler(Type type, JdbcType jdbcType) {
if (ParamMap.class.equals(type)) {
// 忽略 MyBatis 的 Param
return null;
} else if (type == null) {
return getTypeHandler(jdbcType);
}
TypeHandler<?> handler = null;
// 拿到 java 类型对应的所有的处理器
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
// 有具体类型就有限匹配,没有就用 null 兜底,没有 null 就有其他可能得兜底
if (Object.class.equals(type)) {
if (jdbcHandlerMap != null) {
handler = jdbcHandlerMap.get(jdbcType);
}
return handler;
}
if (jdbcHandlerMap != null) {
handler = jdbcHandlerMap.get(jdbcType);
if (handler == null) {
handler = jdbcHandlerMap.get(null);
}
if (handler == null) {
// #591
handler = pickSoleHandler(jdbcHandlerMap);
}
}
if (handler == null) {
handler = getSmartHandler(type, jdbcType);
}
if (handler == null && type instanceof ParameterizedType) {
handler = getTypeHandler((Class<?>) ((ParameterizedType) type).getRawType(), jdbcType);
}
return handler;
}
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
// 先从已注册的 jdbcHandlerMap 去拿,能拿到的话做完防御校验就可以 return
// 没有注册的话就需要调用 getJdbcHandlerMapForSuperclass 方法
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
if (jdbcHandlerMap != null) {
return NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap) ? null : jdbcHandlerMap;
}
if (type instanceof Class) {
Class<?> clazz = (Class<?>) type;
if (!Enum.class.isAssignableFrom(clazz)) {
jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
}
}
typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
return jdbcHandlerMap;
}
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
// 从 typeHandlerMap 中获取对应类的处理器,如何没有就去找他的父类
Class<?> superclass = clazz.getSuperclass();
if (superclass == null || Object.class.equals(superclass)) {
return null;
}
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(superclass);
if (jdbcHandlerMap != null) {
return jdbcHandlerMap;
}
return getJdbcHandlerMapForSuperclass(superclass);
}JavaTypeAliasRegistry
仅使用反射的方式去构建类,就需要写出的类的全类名,还是很长的,因此 MyBatis 帮我们对常用的数据类型注册了别名,并放置在一个 Map<String,Class<?>> 类型的 typeAliases 属性中。然后将 TypeAliasRegitsrty 注册在 Configuration 中。
private final Map<String, Class<?>> typeAliases = new HashMap<>();
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("char", Character.class);
// ......
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
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.put(key, value);
}Java然后提供一个 resolveAlias 方法来从中获取别名对应的 Class 类:
public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// issue #748
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
if (typeAliases.containsKey(key)) {
value = (Class<T>) typeAliases.get(key);
} else {
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}Java
Comments NOTHING