Mybatis源码学习之类型转换(四)
简述
JDBC数据类型与Java语言中的数据类型并不是完全对应的,所以在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换成JDBC类型,而从结果集中获取数据时,则需要从JDBC类型转换成Java类型。MyBatis使用类型处理器完成上述两种转换。
以下是包org.apache.ibatis.type下所有类的继承关系,每一个jdbc类型都对应一个相应的类型转换器(点击图片查看大图)。
JdbcType
在MyBatis中使用JdbcType这个枚举类型
代表JDBC中的数据类型,该枚举类型中定义了TYPE_CODE字段,记录了JDBC类型在java.sql.Types中相应的常量编码,并通过一个静态集合codeLookup(HashMap<Integer,JdbcType>类型)维护了常量编码与JdbcType之间的对应关系,它们的对应关系如下:
{
-1=LONGVARCHAR,
0=NULL,
1=CHAR,
-2=BINARY,
2=NUMERIC,
-3=VARBINARY,
3=DECIMAL,
-4=LONGVARBINARY,
4=INTEGER,
-5=BIGINT,
-6=TINYINT,
5=SMALLINT,
-7=BIT,
6=FLOAT,
70=DATALINK,
7=REAL,
-8=ROWID,
8=DOUBLE,
-9=NVARCHAR,
-10=CURSOR,
12=VARCHAR,
-15=NCHAR,
-16=LONGNVARCHAR,
16=BOOLEAN,
2000=JAVA_OBJECT,
2001=DISTINCT,
2002=STRUCT,
2003=ARRAY,
2004=BLOB,
2005=CLOB,
2006=REF,
1111=OTHER,
2009=SQLXML,
-155=DATETIMEOFFSET,
91=DATE,
2011=NCLOB,
92=TIME,
93=TIMESTAMP,
-2147482648=UNDEFINED
}
TypeHandler
MyBatis中所有的类型转换器都继承了TypeHandler接口,在TypeHandler接口中定义了四个方法,这四个方法分为两类:
- setParameter()方法负责将数据由JdbcType类型转换成Java类型
- getResult()方法及其重载负责将数据由Java类型转换成JdbcType类型
/**
* 类型转换器接口
*
* @author Clinton Begin
*/
public interface TypeHandler<T> {
/**
* 通过PreparedStatement为sql语句绑定参数时,将数据类型由jdbc类型转换成java类型
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 从ResultSet获取结果时,将数据由java类型转换成jdbc类型
*
* @param columnName 列名
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
/**
* @param columnIndex 列索引
*/
T getResult(ResultSet rs, int columnIndex) throws SQLException;
/**
* @param cs 调用数据库中的存储过程并获取返回值
* @param columnIndex 列索引
*/
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
为方便用户自定义TypeHandler实现,MyBatis提供了BaseTypeHandler这个抽象类,它实现了TypeHandler接口,并继承了TypeReference抽象类
在BaseTypeHandler中实现了TypeHandler.setParameter()方法和TypeHandler.getResult()方法。需要注意的是,这两个方法对于非空数据的处理都交给了子类实现。
每个基本类型都会继承BaseTypeHandler实现对应类型的转换,因为实现类比较多,大多是直接调用PreparedStatement 和ResultSet或CallableStatement的对应方法,这里就以 IntegerTypeHandler 为例进行学习:
/**
* Integer类型转换器
* @author Clinton Begin
*/
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
/**
* 参数绑定
* @param i 表示第几个参数
* @param parameter 参数值
* */
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
//调用PreparedStatement.setInt()实现参数绑定
ps.setInt(i, parameter);
}
/**
* 获取指定列值
* @param columnName 列名
* */
@Override
public Integer getNullableResult(ResultSet rs, String columnName)
throws SQLException {
//获取指定列值
return rs.getInt(columnName);
}
/**
* 获取指定列值
* @param columnIndex 列索引
* */
@Override
public Integer getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return rs.getInt(columnIndex);
}
/**
* 获取指定列值
* @param columnIndex 列索引
* */
@Override
public Integer getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getInt(columnIndex);
}
}
TypeHandlerRegistry
MyBatis如何管理众多的TypeHandler接口实现,如何知道何时使用哪个TypeHandler接口实现完成转换呢?这就是TypeHandlerRegistry完成的,在MyBatis初始化过程中,会为所有已知的TypeHandler创建对象,并实现注册到TypeHandlerRegistry中,由TypeHandlerRegistry负责管理这些TypeHandler对象。
首先看一下TypeHandlerRegistry中的几个核心字段,如下:
/**
* 记录JdbcType对应的类型转换对象,从结果中读取数据时将数据由jdbc类型转换成java类型
* */
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
/**
* java类型向指定JdbcType类型转换时,需要使用的TypeHandler对象
* eg:
* java中的String可能转换成数据库中的char、varchar等多种类型,存在一对多的关系
* */
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
/**
* 全部TypeHandler及对应的对象
* */
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
/**
* 空TypeHandler集合标识
* */
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
1、注册TypeHandler对象
TypeHandlerRegistry.register()方法实现了注册TypeHandler对象的功能,该注册过程会向上述四个集合中添加TypeHandler对象。
register()方法有多个重载,这些重载之间的调用关系如下:
从调用关系中可以看出register()方法大多最后都是调用④来完成注册功能的,我们先看一④方法的实现:
/**
* register()重载④
* @param javaType java类型
* @param jdbcType jdbc类型
* @param handler 类型转换器对象
* */
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
//判断指定的java类型是否为null
if (javaType != null) {
//获取指定的Java类型在集合TYPE_HANDLER_MAP中对应的TypeHandler
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
//创建新的TypeHandler集合并添加到TYPE_HANDLER_MAP中
map = new HashMap<JdbcType, TypeHandler<?>>();
TYPE_HANDLER_MAP.put(javaType, map);
}
//将handler对象注册到TYPE_HANDLER_MAP集合中
map.put(jdbcType, handler);
}
//向ALL_TYPE_HANDLERS_MAP集合中注册TypeHandler类型及对应的TypeHandler对象
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
在①~③这个三个register()方法重载中会尝试读取TypeHandler类中定义的@MappedTypes注解和@MappedJdbcTypes注解:
- @MappedTypes注解用于指明该TypeHandler实现类能够处理的Java类型的集合
- @MappedJdbcTypes注解用于指明该TypeHandler实现类能够能够处理的JDBC类型集合
/**
* register()方法重载①的实现
*/
public void register(Class<?> typeHandlerClass) {
boolean mappedTypeFound = false;
//获取注解@MappedTypes
MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
//根据注解中@MappedTypes指定的java类型进行注册
for (Class<?> javaTypeClass : mappedTypes.value()) {
//经过强制类型转换及反射创建TypeHandler对象后,交给重载③处理
register(javaTypeClass, typeHandlerClass);
mappedTypeFound = true;
}
}
if (!mappedTypeFound) {
register(getInstance(null, typeHandlerClass));
}
}
/**
* register()重载②
*/
@SuppressWarnings("unchecked")
public <T> void register(TypeHandler<T> typeHandler) {
boolean mappedTypeFound = false;
//获取注解@MappedTypes,根据注解指定的java类型进行注册,逻辑与①类似
MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> handledType : mappedTypes.value()) {
//交给重载③处理
register(handledType, typeHandler);
mappedTypeFound = true;
}
}
// @since 3.1.0 - try to auto-discover the mapped type
//从3.1.0开始,可以根据TypeHandler类型自动查找对应的java类型,这就要求TypeHandler的实现类同时继承TypeReference抽象类
if (!mappedTypeFound && typeHandler instanceof TypeReference) {
try {
TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
register(typeReference.getRawType(), typeHandler);
mappedTypeFound = true;
} catch (Throwable t) {
// maybe users define the TypeReference with a different type and are not assignable, so just ignore it
}
}
if (!mappedTypeFound) {
//交给重载③处理
register((Class<T>) null, typeHandler);
}
}
/**
* register()重载③
*/
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
//获取注解@MappedTypes,根据注解指定的java类型进行注册
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);
}
}
上述4个register()方法重载都是在向TYPE_HANDLER_MAP集合和ALL_TYPE_ HANDLERS_MAP集合注册TypeHandler对象,而重载⑤是向JDBC_TYPE_HANDLER_MAP集合注册TypeHandler对象,其具体实现如下:
/**
* register()重载⑤
*/
public void register(JdbcType jdbcType, TypeHandler<?> handler) {
//注册JdbcType类型对应的TypeHandler
JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
}
TypeHandlerRegistry除了提供注册单个TypeHandler的register()重载,还可以扫描整个包下的TypeHandler接口实现类,并将完成这些TypeHandler实现类的注册。下面来看重载⑥的具体实现:
/**
* register()重载⑥
* 扫描指定包下TypeHandler的实现类,并完成注册
*/
public void register(String packageName) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
//查找指定包下TypeHandler的实现类
resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
for (Class<?> type : handlerSet) {
//Ignore inner classes and interfaces (including package-info.java) and abstract classes
//过滤掉内部类、接口及抽象类
if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
//交给register()重载①处理
register(type);
}
}
}
最后来看TypeHandlerRegistry构造方法,会通过上述register()方法为基础类型注册对应的TypeHandler对象:
public TypeHandlerRegistry() {
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());
register(Short.class, new ShortTypeHandler());
register(short.class, new ShortTypeHandler());
register(JdbcType.SMALLINT, new ShortTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler());
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
register(Float.class, new FloatTypeHandler());
register(float.class, new FloatTypeHandler());
register(JdbcType.FLOAT, new FloatTypeHandler());
/***********************省略*********************/
}
2、获取TypeHandler对象
TypeHandlerRegistry.getTypeHandler()方法实现了从上述四个集合中获取对应TypeHandler对象的功能。TypeHandlerRegistry.getTypeHandler()方法有多个重载,这些重载之间的关系如下:
由上图的调用关系我们可以看出,经过一系列类型转换之后,TypeHandlerRegistry.getTypeHandler()方法的多个重载都会调用TypeHandlerRegistry.getTypeHandle(Type,JdbcType)这个重载方法,它会根据指定的Java类型和JdbcType类型查找相应的TypeHandler对象,具体实现如下:
/**
* 根据指定的java类型和jdbc类型,查找对应的TypeHandler对象
*/
@SuppressWarnings("unchecked")
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
if (ParamMap.class.equals(type)) {
return null;
}
//根据java类型获取对应jdbc类型的集合jdbcHandlerMap
Map<JdbcType, 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);
}
}
// type drives generics here
return (TypeHandler<T>) handler;
}
在getJdbcHandlerMap()方法中,会检测TYPE_HANDLER_MAP集合中指定Java类型对应的TypeHandler集合是否已经初始化。如果未初始化,则尝试以该Java类型的、已初始化的父类对应的TypeHandler集合为初始集合;如不存在已初始化的父类,则将其对应的TypeHandler集合初始化为NULL_TYPE_HANDLER_MAP标识。
getJdbcHandlerMap()方法具体实现如下:
/**
* 根据指定的java类型获取对应的jdbc和TypeHandler的集合
*/
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
//查找指定java类型对应的TypeHandler集合
Map<JdbcType, 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;
//枚举类型处理
if (clazz.isEnum()) {
jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(clazz, clazz);
if (jdbcHandlerMap == null) {
register(clazz, getInstance(clazz, defaultEnumTypeHandler));
return TYPE_HANDLER_MAP.get(clazz);
}
} else {
//查找父类对应的TypeHandler集合
jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
}
}
TYPE_HANDLER_MAP.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
return jdbcHandlerMap;
}
/**
* 获取父类对应的jdbc和TypeHandler集合
*/
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
Class<?> superclass = clazz.getSuperclass();
//父类为null终止
if (superclass == null || Object.class.equals(superclass)) {
return null;
}
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(superclass);
if (jdbcHandlerMap != null) {
return jdbcHandlerMap;
} else {
//递归查找父类对应的集合
return getJdbcHandlerMapForSuperclass(superclass);
}
}
最后,除了MyBatis本身提供的TypeHandler实现,我们也可以添加自定义的TypeHandler接口实现,添加方式是在mybatis-config.xml配置文件中的<typeHandlers>节点下,添加相应的<typeHandler>节点配置,并指定自定义的TypeHandler接口实现类。在MyBatis初始化时会解析该节点,并将该TypeHandler类型的对象注册到TypeHandlerRegistry中,以供MyBatis后续使用。
TypeAliasRegistry
我们在使用mybatis时,经常会对表名或列名起一些别名,那么mybatis是如何识别别名与原始名字的对应关系呢,这就是接下来要学习的TypeAliasRegistry的作用了。
MyBatis通过TypeAliasRegistry类完成别名注册和管理的功能,TypeAliasRegistry的结构比较简单,它通过TYPE_ALIASES字段(Map<String, Class<?>>类型)管理别名与Java类型之间的对应关系,通过TypeAliasRegistry.registerAlias()方法完成注册别名,该方法的具体实现如下:
/**
* 注册别名
*
* @param alias 别名
* @param value 类
*/
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集合,完成注册
TYPE_ALIASES.put(key, value);
}
在TypeAliasRegistry的构造方法中,默认为Java的基本类型及其数组类型、基本类型的封装类及其数组类型、Date、BigDecimal、BigInteger、Map、HashMap、List、ArrayList、Collection、Iterator、ResultSet等类型添加了别名。
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);
..................省略.................
}
TypeAliasRegistry中还有两个方法需要介绍一下:
- registerAliases(String,Class<?>)重载会扫描指定包下所有的类,并为指定类的子类添加别名;
- registerAlias(Class<?>)重载中会尝试读取@Alias注解
/**
* 注册别名
*
* @param packageName 包名
* @param superType 父类类型
*/
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
//查找指定包下superType类型的类
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for (Class<?> type : typeSet) {
// Ignore inner classes and interfaces (including package-info.java)
// Skip also inner classes. See issue #6
//过滤掉内部类、接口及抽象类
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
}
/**
* 注册别名
*
* @param type 普通类类型
*/
public void registerAlias(Class<?> type) {
//类名(不包括包名)
String alias = type.getSimpleName();
//获取@Alias注解
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
//获取@Alias中的别名
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
}