myBatis原始碼解析-型別轉換篇(5)

超人小冰發表於2020-08-14

前言

開始分析Type包前,說明下使用場景。資料構建語句使用PreparedStatement,需要輸入的是jdbc型別,但我們一般寫的是java型別。同理,資料庫結果集返回的是jdbc型別,而我們需要java型別。這就涉及到一個型別轉換問題,Type包就是解決這個問題。下面是Type包類圖所在結構:

 

 

 

 原始碼解析

1. BaseTypeHandle<T> - 型別處理器實現的基類

mybatis中的預設型別處理器,自定義型別處理器都繼承自BaseTypeHandle。是分析型別處理器的關鍵,檢視其類圖如下:

 

 

 分析BaseTypeHandle<T>前,分析其介面TypeHandle。

// 型別處理器介面,查詢引數時將java型別轉為jdbc型別。獲取結果時將jdbc型別轉問java型別。
public interface TypeHandler<T> {
  // 設定查詢引數,java型別轉為jdbc型別
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  // 獲取結果引數,jdbc轉為java型別
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

TypeHandler介面定義了引數轉換的方法。查詢時將java型別轉為jdbc型別,獲取結果時將jdbc型別轉為java型別。

分析繼承的抽象類,TypeReference<T>,主要是獲取泛型T的原生型別。

public abstract class TypeReference<T> {

  private final Type rawType;   // 儲存所處理的java原生型別、個人理解即T泛型的型別。

  protected TypeReference() {
    rawType = getSuperclassTypeParameter(getClass());
  }
  // rawType的獲取過程。任務型別處理器都需要繼承BaseTypeHandle<T>,而BaseTypeHandle<T>繼承TypeReference<T>,此處為了獲取T的java型別。
  // 至於為什麼使用這一變數,因為我們自定義型別處理器可以不指定java型別,只指定jdbc型別,這樣java型別預設就是T型別。
  Type getSuperclassTypeParameter(Class<?> clazz) {
    Type genericSuperclass = clazz.getGenericSuperclass();  // 獲取分類,包括T。此處和getSuperClass的區別是,getSuperClass只返回直接父類,並不包括父類帶的泛型T
    if (genericSuperclass instanceof Class) {  // 任何型別處理器都有泛型T,一直迴圈找,如果沒找到,直接報錯。
      // 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]; // 獲取泛型T的java型別
    // TODO remove this when Reflector is fixed to return Types
    if (rawType instanceof ParameterizedType) {  // ?自己debug沒有進入到這一步,保留。個人理解是萬一還是型別巢狀模式如user<T>,還有泛型T。就再次獲取T的java型別。
      rawType = ((ParameterizedType) rawType).getRawType();
    }

    return rawType;
  }
  
  public final Type getRawType() {
    return rawType;  // 返回解析的rawType
  }
  .....
}

TypeReference<T>類提供了獲取rawType的方法。對於我們自定義型別轉換器,可以只輸入要轉換的jdbc型別,那預設的待轉換java型別就是rawType型別。

接下來分析BaseTypeHandle<T>,也是一個抽象類,檢視其原始碼,主要實現了對輸入引數,結果引數為空的處理,若不為空,則交給子類具體實現。

// 型別處理器基類,增加了輸入引數為null時的處理
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  protected Configuration configuration;  // 全域性配置引數

  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }

  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {   // 對輸入引數為null時的處理
      if (jdbcType == null) {  // jdbcType不能為空
        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 {
      setNonNullParameter(ps, i, parameter, jdbcType); // 輸入引數不為空的處理
    }
  }
  .......
  // 對非空引數需要子類實現。
  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;

BaseTypeHandler<T>其實只對空資料進行處理,非空資料交給子類實現,此處使用了模板設計模式。檢視具體的型別處理器,如DateTypeHandle進行驗證。

public class DateTypeHandler extends BaseTypeHandler<Date> { // 日期轉換處理器,泛型T為java.util.date

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) 
      throws SQLException {
    ps.setTimestamp(i, new Timestamp((parameter).getTime()));  // 設定PreparedStatement,其實就是java型別轉為資料庫識別的jdbc型別
  }

  @Override
  public Date getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    Timestamp sqlTimestamp = rs.getTimestamp(columnName); // 獲取指定列的結果
    if (sqlTimestamp != null) {
      return new Date(sqlTimestamp.getTime());  // 獲取Date結果,jdbc型別轉為java型別。
    }
    return null;
  }

型別處理器實現也很簡單,是將java資料型別和資料庫識別的jdbc型別的相互轉換。其餘的型別處理器太多了,暫且不分析了,大家感興趣的可以自己看下。

2. TypeHandleRegistry - 型別處理器註冊類

型別轉換處理器定義完畢後,需要有個倉庫進行註冊,後續使用直接去拿即可。TypeHandleRegistry就是處理器註冊的地方。現在對TypeHandleRegistry原始碼進行分析。

private static final Map<Class<?>, Class<?>> reversePrimitiveMap = new HashMap<Class<?>, Class<?>>() {
    private static final long serialVersionUID = 1L;
    {
      put(Byte.class, byte.class);
      put(Short.class, short.class);
      put(Integer.class, int.class);
      put(Long.class, long.class);
      put(Float.class, float.class);
      put(Double.class, double.class);
      put(Boolean.class, boolean.class);
      put(Character.class, char.class);
    }
  };
  // 資料庫型別處理器map集合,jdbc型別為key,TypeHandle為value
  private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
  // java型別處理器map集合,由此可知,一個java型別可以對應對個 jdbc型別
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>();
  // 預設的未知型別處理器
  private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
  // 所有型別的處理器
  private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

分析完具體屬性後,看具體註冊方法。TypeHandleRegistry支援的註冊方法有多種,重寫的register()方法就有很多種。一般註冊我們需要提供轉換的java型別和jdbc型別,具體的轉換器。mybatis除了支援這種預設的註冊方式外,還提供瞭如java型別+處理器,jdbc型別+處理器,單個處理器四種處理方法,一個個分析。

2.1 java型別+jdbc型別+處理器方法

// java type + jdbc type + handler
public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
    register((Type) type, jdbcType, handler);
  }

  private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {  // 根據java型別,jdbc型別,typeHandle來註冊
    if (javaType != null) {  // 若對應的java型別不為空
     // 獲取java型別對應的 jdbc+handle 集合
      Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
      if (map == null) { // 若為空,則建立一個,並將此 jdbc+handle 集合放入TYPE_HANDLER_MAP中
        map = new HashMap<JdbcType, TypeHandler<?>>();
        TYPE_HANDLER_MAP.put(javaType, map);
      }
      map.put(jdbcType, handler);
      // 如果當前的java型別是基本資料型別的包裝類(Integer,Long等),則將其對應的基本資料型別(int,long等)也註冊進去
      if (reversePrimitiveMap.containsKey(javaType)) {
        register(reversePrimitiveMap.get(javaType), jdbcType, handler);
      }
    }
    ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
  }

流程很簡單,其餘的註冊方法基本會呼叫這個方法。流程如下: 

 

 

 2.2 java型別+處理器方法

public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
    register((Type) javaType, typeHandler);
  }

  private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    // 獲取型別處理器中@MappedJdbcTypes註解,該註解作用是定義型別處理器所處理的jdbc型別列表
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) { // 若沒有定義,則預設為空
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { // 一個java型別可對應多個jdbc型別。
        register(javaType, handledJdbcType, typeHandler);
      }
      if (mappedJdbcTypes.includeNullJdbcType()) {
        register(javaType, null, typeHandler);
      }
    } else {
      register(javaType, null, typeHandler);
    }
  }

java型別+處理器型別其實也是呼叫java型別+jdbc型別+處理器註冊方法。只是提供了一個獲取@MappedJdbcTypes註解的功能。此註解用在自定義處理器類,用來獲取處理器指定的jdbc型別。

2.3 jdbc型別+處理器方法

  public void register(JdbcType jdbcType, TypeHandler<?> handler) {
    JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);  // 在map中新增條記錄而已
  }

jdbc型別+處理器方法實際上只是在jdbc與處理器關聯的map中放了條記錄。

2.4 只提供處理器

// Only handler

  @SuppressWarnings("unchecked")
  public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    // 獲取java型別,我們在自定義型別處理器使用@MappedTypes註解來定義我們要轉換的java型別
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) { // 若定義了轉換的java型別
      for (Class<?> handledType : mappedTypes.value()) { // 則遍歷java型別的列表,在呼叫java型別+處理器型別的註冊方法
        register(handledType, typeHandler);
        mappedTypeFound = true; // 找到了待轉換的java型別
      }
    }
    // @since 3.1.0 - try to auto-discover the mapped type
    // 若沒找到待註冊的java型別且繼承了TypeReference<T>。前文分析了,任何一個型別註冊器都會繼承TypeReference<T>,所以後面的判斷條件會為true
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {  
      try {
        TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
        register(typeReference.getRawType(), typeHandler);  // 呼叫typeReference<T>.getRawType()其實獲取的是泛型T的型別
        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); // 呼叫java型別+處理器型別的註冊方法
    }
  }

此方法一般用於使用者註冊自定義處理器。綜上分析,使用者自己實現BaseTypeHandle<T>時,可以使用@MappedTypes註解來指定要轉換的java型別,若沒有指定,則預設為TypeReference<T>泛型T的java型別。也可以使用@MappedJdbcTypes註解來指定要轉換的jdbc型別,若沒有指定,則預設為null。這也是為什麼網上很多自定義型別處理器有的使用了註解,有的沒有使用註解。此處,算是做了一個解釋。另外還需注意的是,通過JDBC_TYPE_HANDLER_MAP這一屬性可知,對於一個java型別,其實支援多種jdbc型別與之對應。

總結

對於型別處理器這塊,總體來說比較簡單,分析起來較輕鬆。本來還打算寫一篇事務包的分析,但發現mybatis其實自身沒有實現任務事務的操作。僅是對資料庫本身事務的簡單封裝。接下來準備開始分析session,execute資料庫執行過程了,還得慢慢來。如覺得還可以,還請看官們點個贊支援下。

相關文章