Type 模块
本文将介绍 MyBatis 中的 JDBC Type 与 Java Type 之间的类型转换模块的实现原理。包括 TypeHandler、BaseTypeHandler 的原理与注册过程
我们知道 JDBC 的数据类型与 Java 语言中的数据类型虽然有对应关系,但并不能做到一一对应,因此也就无法做到自动映射。以下是几个例子:
SQL 类型 | Java 类型 |
---|---|
VARCHAR | java.lang.String |
CHAR | java.lang.String |
BLOB | java.lang.byte[] |
INTEGER UNSIGNED | java.lang.Long |
TINYINT UNSIGNED | java.lang.Integer |
SMALLINT UNSIGNED | java.lang.Integer |
MEDIUMINT UNSIGNED | java.lang.Integer |
BIT | java.lang.Boolean |
BITINT UNSIGNED | java.math.BigInteger |
FLOAT | java.lang.Float |
DOUBLE | java.lang.Double |
DECIMAL | java.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 继承关系图](images/typehandler.webp)
在 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();
jdbcTypeHandlerMap
(Map<JdbcType, TypeHandler<?>>
类型):该集合记录了JdbcType
与TypeHandler
之间的关联关系。JdbcType
是一个枚举类型,每个JdbcType
枚举值对应一种 JDBC 类型,例如,JdbcType.VARCHAR
对应的就是 JDBC 中的 varchar 类型。在从
ResultSet
中读取数据的时候,就会从JDBC_TYPE_HANDLER_MAP
集合中根据 JDBC 类型查找对应的TypeHandler
,将数据转换成 Java 类型。typeHandlerMap
(Map<Type, Map<JdbcType, TypeHandler<?>>>
类型):该集合第一层 Key 是需要转换的 Java 类型,第二层 Key 是转换的目标JdbcType
,最终的 Value 是完成此次转换时所需要使用的TypeHandler
对象。为什么要设计两层 Map 呢?这里我们举个例子:Java 类型中的 String 可能转换成数据库中的 varchar、char、text 等多种类型,存在一对多的关系,所以就可能有不同的
TypeHandler
实现。allTypeHandlersMap
(Map<Class, TypeHandler>
类型):该集合记录了全部TypeHandler
的类型以及对应的TypeHandler
实例对象。NULL_TYPE_HANDLER_MAP
(Map<JdbcType, TypeHandler<?>>
类型):空TypeHandler
集合的标识,默认值为Collections.emptyMap()
。
MyBatis 在初始化的时候,会实例化全部 TypeHandler
对象,并调用 TypeHandlerRegistry
的 register()
完成这些 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
实例的,该功能的具体实现是在 TypeHandlerRegistry
的 getTypeHandler()
方法中。
这里的 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
是维护别名配置的核心实现所在,其中提供了别名注册、别名查询的基本功能。在 TypeAliasRegistry
的 typeAliases
字段(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 的基本类型、基本类型的数组类型、基本类型的封装类、封装类型的数组类型、Date
、BigDecimal
、BigInteger
、Map
、HashMap
、List
、ArrayList
、Collection
、Iterator
、ResultSet
等常用类型添加了别名:
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);
}