天天看點

MyBatis源碼解析之基礎子產品—TypeHandler

MyBatis源碼解析之基礎子產品—TypeHandler

MyBatis源碼解析之基礎子產品—TypeHandler

前文回顧

上一章節我們一起分析了Mybatis的Plugin子產品的源碼。掌握了如何配置攔截器注解,如何自定義攔截器以及攔截器的執行過程。

在使用Mybatis的過程中,基本上我們都要在xml中編寫相應的sql語句以及對應的java屬性與字段的轉換。那麼對于資料庫與java之間的轉換,Mybatis是怎麼做的呢?

接下來本章節我們對MyBatis Type子產品類型轉換的源碼進行分析。

架構設計

按慣例,咱們先了解下Type子產品的總體架構設計。

Type子產品所在包路徑為

org.apache.ibatis.type

,其對應的類架構設計圖如下:

MyBatis源碼解析之基礎子產品—TypeHandler

​ 以上為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 ~