
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 ~