myBatis源碼解析-類型轉換篇(5)

前言java

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

 

 

 

 源碼解析數據庫

1. BaseTypeHandle<T> - 類型處理器實現的基類設計模式

mybatis中的默認類型處理器,自定義類型處理器都繼承自BaseTypeHandle。是分析類型處理器的關鍵,查看其類圖以下:session

 

 

 分析BaseTypeHandle<T>前,分析其接口TypeHandle。mybatis

// 類型處理器接口,查詢參數時將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類型。app

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

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類型。ui

接下來分析BaseTypeHandle<T>,也是一個抽象類,查看其源碼,主要實現了對輸入參數,結果參數爲空的處理,若不爲空,則交給子類具體實現。this

// 類型處理器基類,增長了輸入參數爲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數據庫執行過程了,還得慢慢來。如以爲還能夠,還請看官們點個贊支持下。

相關文章
相關標籤/搜索