MyBatis源码解析之基础模块—TypeHandler
前文回顾
上一章节我们一起分析了Mybatis的Plugin模块的源码。掌握了如何配置拦截器注解,如何自定义拦截器以及拦截器的执行过程。
在使用Mybatis的过程中,基本上我们都要在xml中编写相应的sql语句以及对应的java属性与字段的转换。那么对于数据库与java之间的转换,Mybatis是怎么做的呢?
接下来本章节我们对MyBatis Type模块类型转换的源码进行分析。
架构设计
按惯例,咱们先了解下Type模块的总体架构设计。
Type模块所在包路径为org.apache.ibatis.type
,其对应的类架构设计图如下:
以上为Type模块的架构逻辑,当然针对不同的类型转换实现,架构图中只展示了IntegerTypeHandler、UnknownTypeHandler两个典型实现。
基于架构图,接下来逐个分析其实现源码。
源码解读
JdbcType
JdbcType就是一个枚举类。该类定义了常用的一些数据类型,比如Integer,Double,Date,Date等,基本上满足了我们开发中常用的数据类型。
package org.apache.ibatis.type;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
public enum JdbcType {
INTEGER(Types.INTEGER),
BIGINT(Types.BIGINT),
FLOAT(Types.FLOAT),
REAL(Types.REAL),
DOUBLE(Types.DOUBLE),
NUMERIC(Types.NUMERIC),
DECIMAL(Types.DECIMAL),
CHAR(Types.CHAR),
VARCHAR(Types.VARCHAR),
DATE(Types.DATE),
BOOLEAN(Types.BOOLEAN)
……
; // JDBC 4.2 JDK8
public final int TYPE_CODE;
private static Map<Integer,JdbcType> codeLookup = new HashMap<>();
static {
for (JdbcType type : JdbcType.values()) {
codeLookup.put(type.TYPE_CODE, type);
}
}
JdbcType(int code) {
this.TYPE_CODE = code;
}
public static JdbcType forCode(int code) {
return codeLookup.get(code);
}
}
MappedTypes
该注解接口作用于类型转换的实现类,用于标注要映射的java类型。
public @interface MappedTypes {
/**
* 返回要映射处理的java类型集合
*/
Class<?>[] value();
}
MappedJdbcTypes
该注解接口作用于类型转换的实现类,用于标注要映射的数据库类型。
public @interface MappedJdbcTypes {
/**
* 返回要映射处理的jdbc类型集合
*/
JdbcType[] value();
/**
* 返回是否映射空值 默认false
*/
boolean includeNullJdbcType() default false;
}
关于MappedTypes、MappedJdbcTypes的使用,可参考源码测试中的StringTrimmingTypeHandler类:
@MappedTypes(String.class)
@MappedJdbcTypes(value={JdbcType.CHAR,JdbcType.VARCHAR}, includeNullJdbcType=true)
public class StringTrimmingTypeHandler implements TypeHandler<String> {
//方法实现ain略
}
TypeReference
TypeReference的核心功能是获取类型转换实现类的父类泛型参数类型,听起来貌似有点绕😊。在转换实现类(比如IntegerTypeHandler)在实例化时,会调用TypeReference的构造函数,而该构造函数中会执行获取父类泛型参数类型的方法getSuperclassTypeParameter()。类的详细说明请参看源码注释说明:
package org.apache.ibatis.type;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public abstract class TypeReference<T> {
//原生类型
private final Type rawType;
//构造函数,设置原生类型
protected TypeReference() {
rawType = getSuperclassTypeParameter(getClass());
}
/**
* 功能描述:根据当前类的Class信息获取超类泛型的参数类型(比如IntegerHandlerType的超类泛型参数为Integer)
* @param clazz
* @return
*/
Type getSuperclassTypeParameter(Class<?> clazz) {
Type genericSuperclass = clazz.getGenericSuperclass();
//如果传入类的泛型父类为Class的实例且不为TypeReference类,则已clazz的父类为参数递归调用getSuperclassTypeParameter;否则抛出异常
if (genericSuperclass instanceof Class) {
// try to climb up the hierarchy until meet something useful
if (TypeReference.class != genericSuperclass) {
return getSuperclassTypeParameter(clazz.getSuperclass());
}
throw new TypeException("'" + getClass() + "' extends TypeReference but misses the type parameter. "
+ "Remove the extension or add a type parameter to it.");
}
Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
// TODO remove this when Reflector is fixed to return Types
// 此处貌似说在反射模块中的Reflector修复后会删除如下逻辑(存疑)
if (rawType instanceof ParameterizedType) {
rawType = ((ParameterizedType) rawType).getRawType();
}
return rawType;
}
//获取构造方法中设置的原生类型
public final Type getRawType() {
return rawType;
}
//toString方法返回rawType的toString方法
@Override
public String toString() {
return rawType.toString();
}
}
TypeHandler
TypeHandler为类型转换的核心接口,该接口提供四个方法。
package org.apache.ibatis.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public interface TypeHandler<T> {
//设置参数
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
* 根据ResultSet及columnName获取转换结果
* 请注意:当configuration中的useColumnLabel=false生效,useColumnLabel默认为true(请参看Configuration中的useColumnLabel属性)
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
//根据ResultSet及columnIndex索引获取转换结果
T getResult(ResultSet rs, int columnIndex) throws SQLException;
//根据CallableStatement及columnIndex索引获取转换结果
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
BaseTypeHandler
BaseTypeHandler 该类为抽象类,其继承TypeReference并实现TypeHandler,并且采用模板方法的设计模式,实现了TypeHandler的接口方法的通用逻辑,而相关实现细节则调用定义的抽象方法。由具体的类型转换实现类来实现该方法。
package org.apache.ibatis.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.executor.result.ResultMapException;
import org.apache.ibatis.session.Configuration;
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
/**
* 设置参数
* 1、若parameter为空:
* 1.1、若jdbcType为空,则抛出异常
* 1.2、ps根据索引位置设置对应的字段为空
* 2、若parameter不为空,调用非空参数设置方法进行参数设置
* @param ps
* @param i
* @param parameter
* @param jdbcType
* @throws SQLException
*/
@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;
/**
* @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
* 根据ResultSet及columnName获取转换结果
* 请注意:当configuration中的useColumnLabel=false生效,useColumnLabel默认为true(请参看Configuration中的useColumnLabel属性)
*/
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;
}
IntegerTypeHandler
本次源码分析以IntegerTypeHandler为例对通用类型转换实现进行剖析,该类继承了BaseTypeHandler抽象类,并实现了抽象类的四个抽象方法。MyBatis其他的类型转换类也基本都是同样的实现逻辑。源码如下:
package org.apache.ibatis.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author Clinton Begin
* Integer的类型转换
*/
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
//指定索引位置设置参数
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter);
}
//根据columnName获取结果
@Override
public Integer getNullableResult(ResultSet rs, String columnName)
throws SQLException {
int result = rs.getInt(columnName);
return result == 0 && rs.wasNull() ? null : result;
}
//根据columnIndex获取结果
@Override
public Integer getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
int result = rs.getInt(columnIndex);
return result == 0 && rs.wasNull() ? null : result;
}
//根据CallableStatement及columnIndex获取结果
@Override
public Integer getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
int result = cs.getInt(columnIndex);
return result == 0 && cs.wasNull() ? null : result;
}
}
上面分析的IntegerTypeHandler是对有明确泛型类型的类型转换器,而对没有明确泛型类型的转换器又是怎么处理的呢?接下来咱们分析下UnknownTypeHandler。
UnknownTypeHandler
顾名思义,UnknownTypeHandler表示对没有明确泛型类型的转换。从代码逻辑上看,也是非常清晰的,首先实现了BaseTypeHandler的抽象方法,没个实现方法内部首先要做的就是根据参数找到对应的类型转换器。然后调用具体类型转换器的相应方法,这个设计非常精妙。可以理解为一个通用的路由分发。没有实现的方法中,都会调用相应的TypeHandler解析方法resolveTypeHandler。具体参阅源码:
package org.apache.ibatis.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.Configuration;
public class UnknownTypeHandler extends BaseTypeHandler<Object> {
private static final ObjectTypeHandler OBJECT_TYPE_HANDLER = new ObjectTypeHandler();
private final Supplier<TypeHandlerRegistry> typeHandlerRegistrySupplier;
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
handler.setParameter(ps, i, parameter, jdbcType);
}
/**
* 根据rs columnIndex 获取result
* 1.根据rs中的元数据及columnName参数进行解析,通过resolveTypeHandler获取对应的解析器类型(该方法中一定会给出一个解析器类型)
* 2.调用handler的getResult方法获取结果
* @param rs
* @param columnName
* @return
* @throws SQLException
*/
@Override
public Object getNullableResult(ResultSet rs, String columnName)
throws SQLException {
TypeHandler<?> handler = resolveTypeHandler(rs, columnName);
return handler.getResult(rs, columnName);
}
/**
* 根据rs columnIndex 获取result
* 1.根据rs中的元数据及columnIndex参数进行解析,获取对应的解析器类型,如果没有找到对应的具体解析器,则采用ObjectTypeHandler
* 2.调用handler的getResult方法获取结果
* @param rs
* @param columnIndex
* @return
* @throws SQLException
*/
@Override
public Object getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
TypeHandler<?> handler = resolveTypeHandler(rs.getMetaData(), columnIndex);
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
return handler.getResult(rs, columnIndex);
}
@Override
public Object getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getObject(columnIndex);
}
private TypeHandler<?> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
TypeHandler<?> handler;
if (parameter == null) {
handler = OBJECT_TYPE_HANDLER;
} else {
handler = typeHandlerRegistrySupplier.get().getTypeHandler(parameter.getClass(), jdbcType);
// check if handler is null (issue #270)
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
}
return handler;
}
private TypeHandler<?> resolveTypeHandler(ResultSet rs, String column) {
try {
Map<String,Integer> columnIndexLookup;
columnIndexLookup = new HashMap<>();
ResultSetMetaData rsmd = rs.getMetaData();
int count = rsmd.getColumnCount();
boolean useColumnLabel = config.isUseColumnLabel();
for (int i = 1; i <= count; i++) {
String name = useColumnLabel ? rsmd.getColumnLabel(i) : rsmd.getColumnName(i);
columnIndexLookup.put(name,i);
}
Integer columnIndex = columnIndexLookup.get(column);
TypeHandler<?> handler = null;
if (columnIndex != null) {
handler = resolveTypeHandler(rsmd, columnIndex);
}
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
return handler;
} catch (SQLException e) {
throw new TypeException("Error determining JDBC type for column " + column + ". Cause: " + e, e);
}
}
private TypeHandler<?> resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex) {
TypeHandler<?> handler = null;
JdbcType jdbcType = safeGetJdbcTypeForColumn(rsmd, columnIndex);
Class<?> javaType = safeGetClassForColumn(rsmd, columnIndex);
if (javaType != null && jdbcType != null) {
handler = typeHandlerRegistrySupplier.get().getTypeHandler(javaType, jdbcType);
} else if (javaType != null) {
handler = typeHandlerRegistrySupplier.get().getTypeHandler(javaType);
} else if (jdbcType != null) {
handler = typeHandlerRegistrySupplier.get().getTypeHandler(jdbcType);
}
return handler;
}
…略…
}
TypeAliasRegister
TypeAliasRegister 为JAVA常用数据类型的别名注册器,该类中定义了Map>类型的Map 集合容器,在类构造方法中,会将常用的基本数据类型、基本类型的数组形式及常用集合类型都注册到map 中,同时该类提供了若干个别名注册方法registerAlias。
package org.apache.ibatis.type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.io.Resources;
public class TypeAliasRegistry {
private final Map<String, Class<?>> typeAliases = new HashMap<>();
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);
}
//当类型无法分配时会抛出类型转换异常
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);
}
}
public void registerAliases(String packageName) {
registerAliases(packageName, Object.class);
}
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
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);
}
}
}
/** 根据类名注册别名:
* 1.先获取类的短类名(即不包括类路径)
* 2.获取类的Alias注解
* 3.若Alias注解存在,则别名为注解的值
* 将类注册到 typeAliases 中
*/
public void registerAlias(Class<?> type) {
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
}
//将别名为alias,全路径名为value的类注册到 typeAliases 中
public void registerAlias(String alias, String value) {
try {
registerAlias(alias, Resources.classForName(value));
} catch (ClassNotFoundException e) {
throw new TypeException("Error registering type alias " + alias + " for " + value + ". Cause: " + e, e);
}
}
//别名注册逻辑
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);
}
/**
* 获取类型别名
*/
public Map<String, Class<?>> getTypeAliases() {
return Collections.unmodifiableMap(typeAliases);
}
}
TypeAliasRegister的实例化是在Configuration中定义,当然Mybatis的别名注册器除了在TypeALiasRegister构造函数中进行注册外,在Configuration的构造函数中也进行了其他的别名注册,比如:事务管理方式、数据源、缓存策略、日志组件,代理机制等,具体请看Configuration的构造函数:
public Configuration() {
//注册事务管理(jdbc、managed)
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
//注册所有数据源方式(JNDI、POOLED、UNPOOLED)
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
//注册缓存策略()
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
//注册日志组件
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
//注册代理机制类型(cglib,javaassist)
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
以上分析了类型转换底层支持的源码分析。
TypeHandlerRegister
TypeHandlerRegister为类型转换注册器,该类定义了存放注册器转换的map,定义如下:
//jdbc类型转换器Map,初始化数据来源JdbcType枚举类
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
//java类型转换器Map,数据原来源TypeHandlerRegistry构造函数的初始化
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
//未知类型转换器,在TypeHandlerRegistry构造函数初始化时设值
private final TypeHandler<Object> unknownTypeHandler;
//类型转换器Map,在TypeHandlerRegistry构造函数初始化时设值(初始化时:java类型,jdbc类型,类型处理器均不能为null)
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
//空类型转换器Map
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
到现在可能有些小伙伴会问,我平常开发过程中也没有刻意配置类型转换器,Mybatis怎么就能帮我做正确执行呢?其实在map在初始化时调用构造函数时,Mybatis已经帮我们将常用的TypeHandler进行了注册绑定。关于设置过程比较简单,此处就不做过多分析,有兴趣小伙伴可以参看源码。
TypeHandlerRegister的实例化是在Configuration中定义:
//类型转换注册器实例化
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
ResultSetWrapper
以上介绍了TypeHandler的功能实现,那么这些TypeHandler在哪地方被调用的呢?现在咱们就开始对ResultSetWrapper进行分析。来揭开其神秘面纱。
从该类的命名我们就能大致猜测到该类是对结果的包装处理,这也是类型转换的用武之地。
ResultSetWrapper中定义了几个重要属性:
private final ResultSet resultSet;//返回结果集
private final TypeHandlerRegistry typeHandlerRegistry;//类型注册器
private final List<String> columnNames = new ArrayList<>();//字段名称 List集合
private final List<String> classNames = new ArrayList<>();//类全路径名称 List集合
private final List<JdbcType> jdbcTypes = new ArrayList<>();//jdbcTypes List集合
private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>(); //类型转换Map
private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>(); //被映射的数据库字段名Map
private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>(); //未被映射的数据库字段名Map
ResultSetWrapper有个构造函数,该构造函数有两个参数:ResultSet,Configuration 。在实例化时会从Configuration实例对象中获取类型注册器并赋值给typeHandlerRegistry,并将ResultSet参数赋值给resultSet。同时从ResultSet参数中获取metaData,通过metaData循环将字段名称(或标签)填充到columnNames集合中,字段类型填充到jdbcType集合中,字段对应的java类型填充到classNames集合中。
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
super();
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.resultSet = rs;
final ResultSetMetaData metaData = rs.getMetaData();
final int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
classNames.add(metaData.getColumnClassName(i));
}
}
并提供了根据java属性及数据库字段类型获取对应的类型转换器方法:getTypeHandler(Class<?> propertyType, String columnName)
//通过propertyType、columnName 获取读取结果集是要使用的处理器
public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
TypeHandler<?> handler = null;
Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);
if (columnHandlers == null) {
columnHandlers = new HashMap<>();
typeHandlerMap.put(columnName, columnHandlers);
} else {
handler = columnHandlers.get(propertyType);
}
if (handler == null) {
JdbcType jdbcType = getJdbcType(columnName);
handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
// Replicate logic of UnknownTypeHandler#resolveTypeHandler
// See issue #59 comment 10
if (handler == null || handler instanceof UnknownTypeHandler) {
final int index = columnNames.indexOf(columnName);
final Class<?> javaType = resolveClass(classNames.get(index));
if (javaType != null && jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
} else if (javaType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType);
} else if (jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(jdbcType);
}
}
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = new ObjectTypeHandler();
}
columnHandlers.put(propertyType, handler);
}
return handler;
}
当然ResultSetWrapper只是提供对结果集进行包装及类型转换处理器的获取功能。而真正对结果进行处理,还需要结果处理器来完成。
针对ResultSetHandler等处理器,咱们会在下章节学习Excutor模块时详细进行分析。
总结
本章介绍了Mybatis 类型转换模块的功能:
- 分析了类型转换的架构设计
- 介绍不同数据类型的实现逻辑(采用模板设计思想)
- 分析类型转换、别名的注册逻辑
- 返回结果集包装的逻辑及处理逻辑
关于MyBatis的Type模块介绍至此告一段落。感谢垂阅,如有不妥之处请多多指教~
微观世界,达观人生。
做一名踏实的coder !
欢迎关注我的个人微信公众号:todobugs ~