本文将从以下几个方面进行介绍
类型处理器
别名注册器
前言
JDBC 提供的数据类型和Java的数据类型并不是完全对应的,当
Mybatis
在解析 SQL ,使用
PreparedStatement
来为 SQL 设置参数的时候,需要从 Java 类型转换为 JDBC 的类型,当从
ResultSet
中获取结果的时候,需要中 JDBC 类型转换为 Java 类型;
Mybatis
的类型转换模块就是用来转换这两种数据类型的;比如在写
Mapper
文件的时候,可以有如下写法:
insert id="addUser" parameterType="User"
INSERT INTO user(id, name, age, height) VALUES (
#{id},
#{name, javaType=String, jdbcType=varchar},
#{age, javaType=int, jdbcType=NUMERIC, typeHandler=MyTypeHandler},
#{height, javaType=double, jdbcType=NUMERIC, numericScale=2}
)
/insert
可以指定Java和数据库对应的类型,还可以指定自定义的类型处理器等,在
Mybatis
在解析 SQL 的时候,会通过类型转换处理器进行相应的转换
源码分析
Mybatis
的类型转换相关的代码主要在
type
包下,如下所示:
当然,
type
包下不只是这些类,还有其他的一个内置的类型转换处理器,如
ArrayTypeHandler
等,还有三个类型的注册类
TypeHandlerRegistry
,
SimpleTypeRegistry
和
TypeAliasRegistry
,此外该包下还定义了一些注解等。
类型处理器
TypeHandler 接口
Mybatis
中所有的类型转换器都实现了
TypeHandler
接口,该接口下只有四个方法,共分为两类,一类是将 JDBC 类型转换为 Java 类型,一类是将 Java 类型转换为 JDBC 类型,源码如下:
public interface TypeHandlerT {
// 通过 PreparedStatement 绑定参数时,参数由 Jdbc 类型转换为 Java 类型
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
// 从 ResultSet 获取数据时,数据由 Java 类型转换为 Jdbc类型
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
TypeReference 抽象类
TypeReference
是一个抽象类,它表示引用一个泛型类型:
public abstract class TypeReferenceT {
// 原始类型Type
private final Type rawType;
// 构造,获取原始类型
protected TypeReference() {
rawType = getSuperclassTypeParameter(getClass());
}
// 获取原始类
Type getSuperclassTypeParameter(Class? clazz) {
// 获得带有泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
if (genericSuperclass instanceof Class) {
if (TypeReference.class != genericSuperclass) {
return getSuperclassTypeParameter(clazz.getSuperclass());
}
// 抛异常
}
// 获取到泛型中的原始类型
// ParameterizedType是一个记录类型泛型的接口
// getActualTypeArguments():回泛型类型数组
Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
if (rawType instanceof ParameterizedType) {
rawType = ((ParameterizedType) rawType).getRawType();
}
// 返回原始类型
return rawType;
}
// 返回原始类型
public final Type getRawType() {
return rawType;
}
}
BaseTypeHandler
在
Mybatis
中,提供了
TypeHandler
接口的唯一实现,即
BaseTypeHandler
,主要是为了方便用户的自定义实现
TypeHandler
接口。
在
BaseTypeHandler
抽象类中,实现了
TypeHandler
的
setParameter()
和
getResult()
方法,在这两个方法内部,对于非空数据的处理,由具体的子类进行实现;源码如下:
public abstract class BaseTypeHandlerT extends TypeReferenceT implements TypeHandlerT {
// 表示一个配置文件类
protected Configuration configuration;
public void setConfiguration(Configuration c) {
this.configuration = c;
}
// 为 SQL 设置参数
@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 {
// 如果参数为空,则设置为 null
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException(e);
}
} else {
try {
// 如果参数不为空,则由子类实现,该方法是一个抽象方法
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException(e);
}
}
}
// 从结果集中根据列名获取数据
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
T result;
try {
// 获取结果,由子类实现
result = getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException( e);
}
// 如果为空,则返回 null
if (rs.wasNull()) {
return null;
} else {
return result;
}
}
// 从结果集中根据列索引获取数据
@Override
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
// 同上
}
@Override
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
// 同上
}
// 为 SQL 设置非空的参数,由各个子类自己实现
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;
}
StringTypeHandler
在上面的
BaseTypeHandle
r 抽象类中,为
SQL
设置参数和或结果集中获取数据,相应的都交由子类去实现,它大概有
31
个实现类,现在以
StringTypeHandler
为例,看下它是怎么实现的;
public class StringTypeHandler extends BaseTypeHandlerString {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getString(columnIndex);
}
}
可以看到,String 类型的类型处理器会调用
PreparedStatement
的
setString
方法绑定参数,调用
ResultSet
的
getString
获取结果,其他的实现类大概都如此。
类型注册器 TypeHandlerRegistry
在
Mybatis
初始化的时候,会为所有已知的类型处理器
TypeHandler
创建对象,并注册到
TypeHandlerRegistry
中,由
TypeHandlerRegistry
来管理这些对象,接下来看下
TypeHandlerRegistry
的源码:
在
TypeHandlerRegistry
中定义了几个 Map 集合来存放相应的
TypeHandler
对象,如下所示:
// Jdbc 类型和类型处理器 TypeHandler 的对应关系
// 该集合主要用于从结果集中读取数据时,从 Jdbc 类型转换为 Java 类型
private final MapJdbcType, TypeHandler? JDBC_TYPE_HANDLER_MAP = new EnumMapJdbcType, TypeHandler?(JdbcType.class);
// Java类型和Jdbc类型的对应关系,当Java类型向指定的Jdbc类型转换时,需要使用的 TypeHandler 对象
// 一种Java类型可以对应多种Jdbc 类型,如 String 对应 char 和 varchar
private final MapType, MapJdbcType, TypeHandler? TYPE_HANDLER_MAP = new ConcurrentHashMapType, MapJdbcType, TypeHandler?();
// 为知类型,当找不到对应类型时,使用该类型,也就是 ObjectTypeHandler
private final TypeHandlerObject UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
// 存放全部的 TypeHandler 类型以及该类型相应的 TypeHandler 对象
private final MapClass?, TypeHandler? ALL_TYPE_HANDLERS_MAP = new HashMapClass?, TypeHandler?();
// 空类型
private static final MapJdbcType, TypeHandler? NULL_TYPE_HANDLER_MAP = new HashMapJdbcType, TypeHandler?();
注册 TypeHandler
在创建该对象的时候,会对这些类型处理器进行注册:
public TypeHandlerRegistry() {
// 多种Java类型可以对应一种类型处理器
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());
// 一种 Java 类型可以对应多种处理器
register(Date.class, new DateTypeHandler());
register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
// ......... 注册其他类型........
}
接下来看下这些类型处理器是如何注册的,即把相应的类型处理器存放到 上述定义的几个 Map 中去,在
TypeHandlerRegistry
中定义了 12 个重载的
register()
方法进行注册,下面看下几个主要的方法实现:
// 注册 Jdbc 类型和相应的处理器
public void register(JdbcType jdbcType, TypeHandler? handler) {
JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
}
// 注册 Java 类型和相应的处理器
private void register(Type javaType, TypeHandler? extends typeHandler) {
// 处理注解的情况
MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
if (mappedJdbcTypes != null) {
for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
register(javaType, handledJdbcType, typeHandler);
}
if (mappedJdbcTypes.includeNullJdbcType()) {
register(javaType, null, typeHandler);
}
} else {
register(javaType, null, typeHandler);
}
}
// 根据 Java 类型 Jdbc 类型和类型处理器进行相应的注册
private void register(Type javaType, JdbcType jdbcType, TypeHandler? handler) {
if (javaType != null) {
// 根据 Java 类型获取对应的 Jdbc 类型
MapJdbcType, TypeHandler? map = TYPE_HANDLER_MAP.get(javaType);
// 如果为空,则新建一个
if (map == null) {
map = new HashMapJdbcType, TypeHandler?();
TYPE_HANDLER_MAP.put(javaType, map);
}
// 注册
map.put(jdbcType, handler);
}
// 同时注册类型处理器类和对象的对应关系
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
当注册完这些类型处理器对象后,如何去查找相应的类型处理器呢,
TypeHandlerRegistry
也提供了相应的方法来进行查找,提供了
6
个重载的
getTypeHandler
方法,根据 Java 类型和 Jdbc 类型查找对应的
TypeHandler
对象:
// 查找或初始化 Java 类型对应的 TypeHandler 集合
private TypeHandler getTypeHandler(Type type, JdbcType jdbcType) {
// 根据 Java 类型查找对应的 TypeHandler 集合
MapJdbcType, TypeHandler? jdbcHandlerMap = getJdbcHandlerMap(type);
TypeHandler? handler = null;
if (jdbcHandlerMap != null) {
// 根据 Jdbc 类型查找 TypeHandler 对象
handler = jdbcHandlerMap.get(jdbcType);
if (handler == null) {
handler = jdbcHandlerMap.get(null);
}
if (handler == null) {
// 如果 jdbcHandlerMap 只注册了一个 TypeHandler 对象,则使用此 TypeHandler 对象
handler = pickSoleHandler(jdbcHandlerMap);
}
}
return (TypeHandler) handler;
}
// 根据 Java 类型查找对应的 TypeHandler 集合
private MapJdbcType, TypeHandler? getJdbcHandlerMap(Type type) {
// 去 TYPE_HANDLER_MAP 进行查找
MapJdbcType, TypeHandler? jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
// 检测是否为空集合
if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
return null;
}
// 初始化指定 Java 类型的 TypeHandler 集合
if (jdbcHandlerMap == null && type instanceof Class) {
Class? clazz = (Class?) type;
// 查找父类对应的 TypeHandler 集合,并作为初始集合
jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
if (jdbcHandlerMap != null) {
TYPE_HANDLER_MAP.put(type, jdbcHandlerMap);
} else if (clazz.isEnum()) {
// 枚举类的处理,进行注册
register(clazz, new EnumTypeHandler(clazz));
return TYPE_HANDLER_MAP.get(clazz);
}
}
if (jdbcHandlerMap == null) {
TYPE_HANDLER_MAP.put(type, NULL_TYPE_HANDLER_MAP);
}
return jdbcHandlerMap;
}
上述就是类型注册器
TypeHandlerRegistry
的一个实现过程
别名注册器 TypeAliasRegistry
在编写
Mapper SQL
的时候,可以使用别名,比如,
select id="findByName" resultType="map" parameterType="int"
然后的解析 SQL 的时候,就可以获取对应的类型,如
Java.util.Map
,
Java.lang.Integer
等。
Mybatis
通过
TypeAliasRegistry
来完成别名的注册和管理功能。
该方法比较简单,它提供了
5
个重载的
registerAlias
方法来进行别名的注册,提供一个方法
resolveAlias
来解析别名,最后在构造方法中进行别名的注册:
private final MapString, Class? TYPE_ALIASES = new HashMapString, Class?();
// 注册别名
public void registerAlias(String alias, Class? value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// 将名称转换为小写
String key = alias.toLowerCase(Locale.ENGLISH);
// 判断名称是否存在,如果别名已存在,且对应的类型不一致,则抛异常
if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
}
// 注册,别名和类型的对应关系
TYPE_ALIASES.put(key, value);
}
public void registerAlias(Class? type) {
String alias = type.getSimpleName();
// 处理 @Alias 注解的情况
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
}
解析别名:
// 解析别名
public Class resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// 别名转换为小写,因为在注册的时候,转换过
String key = string.toLowerCase(Locale.ENGLISH);
Class value;
// 如果该别名已经注册,则获取对应的类型
if (TYPE_ALIASES.containsKey(key)) {
value = (Class) TYPE_ALIASES.get(key);
} else {
// 尝试使用反射来获取类型
value = (Class) Resources.classForName(string);
}
// 返回对应的类型
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
注册别名,接下来就是进行别名的注册,通过构造方法进行注册
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
以上就是 Mybatis 进行类型转换的一个主要代码逻辑,还是挺好理解的。
相关文章
原文始发于微信公众号(Java技术大杂烩):