Mybatis 類型轉換源碼分析

本文將從如下幾個方面進行介紹

相關文章java

前言數據庫

類型處理器數組

類型註冊器app

別名註冊器ide

相關文章

Mybatis 解析配置文件的源碼解析源碼分析

前言

JDBC 提供的數據類型和Java的數據類型並非徹底對應的,當 Mybatis 在解析 SQL ,使用 PreparedStatement 來爲 SQL 設置參數的時候,須要從 Java 類型轉換爲 JDBC 的類型,當從 ResultSet 中獲取結果的時候,須要中 JDBC 類型轉換爲 Java 類型;Mybatis 的類型轉換模塊就是用來轉換這兩種數據類型的;好比在寫 Mapper 文件的時候,能夠有以下寫法:ui

<insert id="addUser" parameterType="User">
		INSERT INTO user(id, name, age, height) VALUES (
		#{id},
		#{name, javaType=String, jdbcType=varchar},
		#{age, javaType=int, jdbcType=NUMERIC, typeHandler=MyTypeHandler},
		#{height, javaType=double, jdbcType=NUMERIC, numericScale=2}
		)
	</insert>

能夠指定Java和數據庫對應的類型,還能夠指定自定義的類型處理器等,在 Mybatis 在解析 SQL 的時候,會經過類型轉換處理器進行相應的轉換this

源碼分析

Mybatis 的類型轉換相關的代碼主要在 type 包下,以下所示:spa

固然,type 包下不僅是這些類,還有其餘的一個內置的類型轉換處理器,如 ArrayTypeHandler 等,還有三個類型的註冊類 TypeHandlerRegistrySimpleTypeRegistry 和 TypeAliasRegistry ,此外該包下還定義了一些註解等。.net

TypeHandler 接口

Mybatis 中全部的類型轉換器都實現了 TypeHandler 接口,該接口下只有四個方法,共分爲兩類,一類是將 JDBC 類型轉換爲 Java 類型,一類是將 Java 類型轉換爲 JDBC 類型,源碼以下:

public interface TypeHandler<T> {
  // 經過 PreparedStatement 綁定參數時,參數由 Jdbc 類型轉換爲 Java 類型
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  
  // 從 ResultSet 獲取數據時,數據由 Java 類型轉換爲 Jdbc類型
  T getResult(ResultSet rs, String columnName) throws SQLException;

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

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

}

TypeReference 抽象類

TypeReference 是一個抽象類,它表示引用一個泛型類型:

public abstract class TypeReference<T> {
  // 原始類型Type
  private final Type rawType;
  // 構造,獲取原始類型
  protected TypeReference() {
    rawType = getSuperclassTypeParameter(getClass());
  }
  
  // 獲取原始來下
  Type getSuperclassTypeParameter(Class<?> clazz) {
    // 得到帶有泛型的父類
    Type genericSuperclass = clazz.getGenericSuperclass();
    if (genericSuperclass instanceof Class) {
      if (TypeReference.class != genericSuperclass) {
        return getSuperclassTypeParameter(clazz.getSuperclass());
      }
     // 拋異常
    }
    // 獲取到泛型中的原始類型
    // ParameterizedType是一個記錄類型泛型的接口
    // getActualTypeArguments():回泛型類型數組
    Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
    if (rawType instanceof ParameterizedType) {
      rawType = ((ParameterizedType) rawType).getRawType();
    }
    // 返回原始類型
    return rawType;
  }
 // 返回原始類型
  public final Type getRawType() {
    return rawType;
  }
}

BaseTypeHandler 

在 Mybatis 中,提供了 TypeHandler 接口的惟一實現,即 BaseTypeHandler ,主要是爲了方便用戶的自定義實現 TypeHandler 接口。

在 BaseTypeHandler 抽象類中,實現了 TypeHandler 的 setParameter() 和 getResult() 方法,在這兩個方法內部,對於非空數據的處理,由具體的子類進行實現;源碼以下:

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
  // 表示一個配置文件類
  protected Configuration configuration;

  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }
  // 爲 SQL 設置參數
  @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 {
        // 若是參數爲空,則設置爲 null
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException(e);
      }
    } else {
      try {
        // 若是參數不爲空,則由子類實現,該方法是一個抽象方法
        setNonNullParameter(ps, i, parameter, jdbcType);
      } catch (Exception e) {
        throw new TypeException(e);
      }
    }
  }
  // 從結果集中根據列名獲取數據
  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    T result;
    try {
      // 獲取結果,由子類實現
      result = getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException( e);
    }
    // 若是爲空,則返回 null
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  // 從結果集中根據列索引獲取數據
  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    //   同上
  }
  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    //   同上
  }  
  // 爲 SQL 設置非空的參數,由各個子類本身實現
  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;
}

StringTypeHandler

在上面的 BaseTypeHandler 抽象類中,爲 SQL 設置參數和或結果集中獲取數據,相應的都交由子類去實現,它大概有 31 個實現類,如今以StringTypeHandler 爲例,看下它是怎麼實現的;

public class StringTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getString(columnIndex);
  }
}

能夠看到,String 類型的類型處理器會調用 PreparedStatement 的 setString 方法綁定參數,調用 ResultSet 的 getString 獲取結果,其餘的實現類大概都如此。

TypeHandlerRegistry

在 Mybatis 初始化的時候,會爲全部已知的類型處理器 TypeHandler 建立對象,並註冊到 TypeHandlerRegistry 中,由 TypeHandlerRegistry 來管理這些對象,接下來看下 TypeHandlerRegistry 的源碼:

在 TypeHandlerRegistry 中定義了幾個 Map 集合來存放相應的 TypeHandler 對象,以下所示:

// Jdbc 類型和類型處理器 TypeHandler 的對應關係
// 該集合主要用於從結果集中讀取數據時,從 Jdbc 類型轉換爲 Java 類型
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);

// Java類型和Jdbc類型的對應關係,當Java類型向指定的Jdbc類型轉換時,須要使用的 TypeHandler 對象
// 一種Java類型能夠對應多種Jdbc 類型,如 String 對應 char 和 varchar
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();

// 爲知類型,當找不到對應類型時,使用該類型,也就是 ObjectTypeHandler
private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);

// 存放所有的 TypeHandler 類型以及該類型相應的 TypeHandler 對象
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

// 空類型
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = new HashMap<JdbcType, TypeHandler<?>>();

註冊 TypeHandler 

在建立該對象的時候,會對這些類型處理器進行註冊:

public TypeHandlerRegistry() {
    // 多種Java類型能夠對應一種類型處理器
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
    // ......... 註冊其餘類型........
    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());

    // 一種 Java 類型能夠對應多種處理器
    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    // ......... 註冊其餘類型........
}

接下來看下這些類型處理器是如何註冊的,即把相應的類型處理器存放到 上述定義的幾個 Map 中去,在 TypeHandlerRegistry  中定義了  12 個重載的 register() 方法進行註冊,下面看下幾個主要的方法實現:

// 註冊 Jdbc 類型和相應的處理器
  public void register(JdbcType jdbcType, TypeHandler<?> handler) {
    JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
  }

  // 註冊 Java 類型和相應的處理器
  private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    // 處理註解的狀況
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
        register(javaType, handledJdbcType, typeHandler);
      }
      if (mappedJdbcTypes.includeNullJdbcType()) {
        register(javaType, null, typeHandler);
      }
    } else {
      register(javaType, null, typeHandler);
    }
  }
  // 根據 Java 類型 Jdbc 類型和類型處理器進行相應的註冊
  private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      // 根據 Java 類型獲取對應的 Jdbc 類型
      Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
      // 若是爲空,則新建一個
      if (map == null) {
        map = new HashMap<JdbcType, TypeHandler<?>>();
        TYPE_HANDLER_MAP.put(javaType, map);
      }
      // 註冊
      map.put(jdbcType, handler);
    }
    // 同時註冊類型處理器類和對象的對應關係
    ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
  }

當註冊完這些類型處理器對象後,如何去查找相應的類型處理器呢,TypeHandlerRegistry  也提供了相應的方法來進行查找,提供了 6 個重載的 getTypeHandler 方法,根據 Java 類型和 Jdbc 類型查找對應的 TypeHandler 對象:

// 查找或初始化 Java 類型對應的 TypeHandler 集合
  private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    // 根據 Java 類型查找對應的 TypeHandler 集合
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
      // 根據 Jdbc 類型查找 TypeHandler  對象
      handler = jdbcHandlerMap.get(jdbcType);
      if (handler == null) {
        handler = jdbcHandlerMap.get(null);
      }
      if (handler == null) {
        // 若是 jdbcHandlerMap 只註冊了一個 TypeHandler 對象,則使用此 TypeHandler 對象
        handler = pickSoleHandler(jdbcHandlerMap);
      }
    }
    return (TypeHandler<T>) handler;
  }

  // 根據 Java 類型查找對應的 TypeHandler 集合
  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
    // 去 TYPE_HANDLER_MAP 進行查找
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
    // 檢測是否爲空集合
    if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
      return null;
    }
    // 初始化指定 Java 類型的 TypeHandler 集合
    if (jdbcHandlerMap == null && type instanceof Class) {
      Class<?> clazz = (Class<?>) type;
      // 查找父類對應的 TypeHandler 集合,並做爲初始集合
      jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
      if (jdbcHandlerMap != null) {
        TYPE_HANDLER_MAP.put(type, jdbcHandlerMap);
      } else if (clazz.isEnum()) {
        // 枚舉類的處理,進行註冊
        register(clazz, new EnumTypeHandler(clazz));
        return TYPE_HANDLER_MAP.get(clazz);
      }
    }
    if (jdbcHandlerMap == null) {
      TYPE_HANDLER_MAP.put(type, NULL_TYPE_HANDLER_MAP);
    }
    return jdbcHandlerMap;
  }

上述就是類型註冊器 TypeHandlerRegistry  的一個實現過程。

TypeAliasRegistry

在編寫 Mapper SQL 的時候,可使用別名,好比,

<select id="findByName" resultType="map" parameterType="int">

而後的解析 SQL 的時候,就能夠獲取對應的類型,如 Java.util.Map, Java.lang.Integer 等。Mybatis 經過 TypeAliasRegistry 來完成別名的註冊和管理功能。

該方法比較簡單,它提供了 5 個重載的 registerAlias 方法來進行別名的註冊,提供一個方法 resolveAlias 來解析別名,最後在構造方法中進行別名的註冊:

private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

  // 註冊別名
  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // 將名稱轉換爲小寫
    String key = alias.toLowerCase(Locale.ENGLISH);
    // 判斷名稱是否存在,若是別名已存在,且對應的類型不一致,則拋異常
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    // 註冊,別名和類型的對應關係
    TYPE_ALIASES.put(key, value);
  }

  public void registerAlias(Class<?> type) {
    String alias = type.getSimpleName();
    // 處理 @Alias 註解的狀況
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      alias = aliasAnnotation.value();
    } 
    registerAlias(alias, type);
  }

解析別名:

// 解析別名
  public <T> Class<T> resolveAlias(String string) {
    try {
      if (string == null) {
        return null;
      }
      // 別名轉換爲小寫,由於在註冊的時候,轉換過
      String key = string.toLowerCase(Locale.ENGLISH);
      Class<T> value;
      // 若是該別名已經註冊,則獲取對應的類型
      if (TYPE_ALIASES.containsKey(key)) {
        value = (Class<T>) TYPE_ALIASES.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 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);
  }

 

以上就是 Mybatis 進行類型轉換的一個主要代碼邏輯,仍是挺好理解的。

相關文章
相關標籤/搜索