mybatis源碼解析之 TypeHandler

TypeHanler

MyBatis 在預處理語句(PreparedStatement)中設置一個參數或者從結果集中取出一個值時,都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。Mybatis默認爲我們實現了許多TypeHandler, 當我們沒有配置指定TypeHandler時,Mybatis會根據參數或者返回結果的不同,默認爲我們選擇合適的TypeHandler處理。

在MyBatis中,StatementHandler負責對需要執行的SQL語句進行預編譯處理,主要完成以下兩項工作:
1.調用參數處理器(ParameterHandler)來設置需要傳入SQL的參數;
2.調用結果集處理器(ResultSetHandler)來處理查詢到的結果數據;

而不管要完成其中的哪一項工作都需要使用類型處理器(TypeHandler)來進行數據類型處理,無論是ParameterHandler爲預處理語句(PreparedStatement)設置一個參數,還是ResultSetHandler將結果集中的一條記錄轉換爲合適的Java類型。在整個過程中,TypeHandler負責完成數據庫類型與JavaBean類型的轉換工作。

配置示例


<typeHandlers>
    
	<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>

	<!-- javaType 配置java類型,例如String, 如果配上javaType, 那麼指定的typeHandler就只作用於指定的類型 -->
	<typeHandler handler="" javaType=""/>
    
	<!-- jdbcType 配置數據庫基本數據類型,例如varchar, 如果配上jdbcType, 那麼指定的typeHandler就只作用於指定的類型  -->
	<typeHandler jdbcType="" handler=""/>
    
	<!-- 也可兩者都配置 -->
    <typeHandler javaType="" jdbcType="" handler=""/>
    
    <!-- 掃描包的方式 -->
	<package name="org.mybatis.example"/>
</typeHandlers>

實現原理

Map<Class<?>, TypeHandler< ? >> ALL_TYPE_HANDLERS_MAP:所有 TypeHandler 的類名和類的映射關係
Map<Type, Map<JdbcType, TypeHandler < ? >>>  TYPE_HANDLER_MAP:所有 Type 管理的映射關係
Map<JdbcType, TypeHandler< ? >> JDBC_TYPE_HANDLER_MAP:

配置解析

  private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //如果是 package,找到 name 對應的包名。掃描包下所有非接口、非匿名、非抽象類。
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          //當 javaTypeName 爲類的全路徑或者是已經註冊的類的別名的時候不爲空
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          //jdbcTypeName 必須爲 JdbcType 支持的類型
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          //當 handlerTypeName 爲類的全路徑或者是已經註冊的類的別名的時候不爲空
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              //
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            //掃描 typeHandlerClass 的 MapperType 註解
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

!處理流程

查找某個類的 TypeHandler

在實際配置文件中,會根據 jdbcType 和 javaType 找對應的 TypeHandler,查詢過程如下:

  1. 如果不是 Enum 類型:從當前類及其父類依次向上查找,知道找到對應的 TypeHandler
  2. 如果是 Enum 類型:採用深度優先的算法從 Enum 的接口中查找。
  private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    if (ParamMap.class.equals(type)) {
      return null;
    }
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
      handler = jdbcHandlerMap.get(jdbcType);
      if (handler == null) {
        handler = jdbcHandlerMap.get(null);
      }
      if (handler == null) {
        // #591
        handler = pickSoleHandler(jdbcHandlerMap);
      }
    }
    // type drives generics here
    return (TypeHandler<T>) handler;
  }

  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
    if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
      return null;
    }
    if (jdbcHandlerMap == null && type instanceof Class) {
      Class<?> clazz = (Class<?>) type;
      //Enum 類型特殊處理
      if (clazz.isEnum()) {
        jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(clazz, clazz);
        if (jdbcHandlerMap == null) {
          register(clazz, getInstance(clazz, defaultEnumTypeHandler));
          return TYPE_HANDLER_MAP.get(clazz);
        }
      } else { //非 Enum 類型,會從父類一直查找。
        jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
      }
    }
    TYPE_HANDLER_MAP.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
    return jdbcHandlerMap;
  }

  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForEnumInterfaces(Class<?> clazz, Class<?> enumClazz) {
    for (Class<?> iface : clazz.getInterfaces()) {
      Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(iface);
      if (jdbcHandlerMap == null) {
        //深度優先遍歷查找
        jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(iface, enumClazz);
      }
      if (jdbcHandlerMap != null) {
        // Found a type handler regsiterd to a super interface
        HashMap<JdbcType, TypeHandler<?>> newMap = new HashMap<>();
        for (Entry<JdbcType, TypeHandler<?>> entry : jdbcHandlerMap.entrySet()) {
          // Create a type handler instance with enum type as a constructor arg
          newMap.put(entry.getKey(), getInstance(enumClazz, entry.getValue().getClass()));
        }
        return newMap;
      }
    }
    return null;
  }

  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
    Class<?> superclass =  clazz.getSuperclass();
    if (superclass == null || Object.class.equals(superclass)) {
      return null;
    }
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(superclass);
    if (jdbcHandlerMap != null) {
      return jdbcHandlerMap;
    } else {
      //遞歸查找
      return getJdbcHandlerMapForSuperclass(superclass);
    }
  }

  private TypeHandler<?> pickSoleHandler(Map<JdbcType, TypeHandler<?>> jdbcHandlerMap) {
    TypeHandler<?> soleHandler = null;
    for (TypeHandler<?> handler : jdbcHandlerMap.values()) {
      if (soleHandler == null) {
        soleHandler = handler;
      } else if (!handler.getClass().equals(soleHandler.getClass())) {
        // More than one type handlers registered.
        return null;
      }
    }
    return soleHandler;
  }

備註:

  1. typeHandler標籤支持配置文件和註解兩種方式配置 JavaType 和 JdbcType。配置文件的優先級高於註解的優先級。推薦用註解的方式
  2. package 標籤只支持註解的方式配置javaType 和 JdbcType。
  3. 在查找 Type 的時候,如果當前沒有對應的 Map<JdbcType, TypeHandler<?>>,會繼續從父類中找,直到 Object 向上找。
  4. 如果 Type 爲 Enum 接口,採用深度優先遍歷查找。非 Enum 類型,採用遞歸向上直到 Object。
  5. @MappedTypes 或 @MappedJdbcTypes 的值爲數組。因此,支持多個類型的。問題:什麼時候可以給 MapperTypes 和 MappedJdbcTypes 配置多個值?

自定義 TypeHandler

(1)創建一個繼承自 BaseType 的自定義typeHandler的類;

(2)在Mybatis配置文件或註解中中配置類型處理器

(3)在引射器的XML配置中標識需要用自定義typeHandler處理的參數或者結果。

在自定義 TypeHandler 的時候,簡單得繼承 BaseTypeHandler 即可。

TypeHandler

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  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 都必須實現接口 TypeHandler

BaseTypeHandler

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
    
  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;

}

mybatis 默認的 BaseTypeHandler 統一了異常處理,實現者自定義 TypHanlder 應該繼承BaseTypeHandler 而不是TypeHandler

StringTypeHandler


@MappedJdbcType(String.class)
public class StringTypeHandler extends BaseTypeHandler<String> {

  //預處理的的時候,將 Java 類型 parameter 轉換爲 JDBC 類型
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setString(i, parameter);
  }
   
  //將 JDBC 類型轉換爲 Java 類型
  @Override
  public String getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getString(columnName);
  }

  //將 JDBC 類型轉換爲 Java 類型
  @Override
  public String getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getString(columnIndex);
  }

  //將 JDBC 類型轉換爲 Java 類型
  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getString(columnIndex);
  }
}

這裏 Jdbc 類型爲 VARCHAR,Java 類型爲 String。

問題:如何自定義 TypeHandler 將 Enum 類轉換爲 int 類型。 答案參考 EnumOrdinalTypeHandler

<resultMap type="cn.don.pojo.Student" id="queryStudentByNumResult">
	<result property="name" column="name" typeHandler="StringTypeHandler"/>
</resultMap>

參考

簡單的例子:https://blog.csdn.net/u012525096/article/details/82459455

這篇文章的例子不錯:https://elim.iteye.com/blog/1847854

附錄

    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());

    register(Short.class, new ShortTypeHandler());
    register(short.class, new ShortTypeHandler());
    register(JdbcType.SMALLINT, new ShortTypeHandler());

    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());

    register(Long.class, new LongTypeHandler());
    register(long.class, new LongTypeHandler());

    register(Float.class, new FloatTypeHandler());
    register(float.class, new FloatTypeHandler());
    register(JdbcType.FLOAT, new FloatTypeHandler());

    register(Double.class, new DoubleTypeHandler());
    register(double.class, new DoubleTypeHandler());
    register(JdbcType.DOUBLE, new DoubleTypeHandler());

    register(Reader.class, new ClobReaderTypeHandler());
    register(String.class, new StringTypeHandler());
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());
    register(JdbcType.CLOB, new ClobTypeHandler());
    register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
    register(JdbcType.NCHAR, new NStringTypeHandler());
    register(JdbcType.NCLOB, new NClobTypeHandler());

    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
    register(JdbcType.ARRAY, new ArrayTypeHandler());

    register(BigInteger.class, new BigIntegerTypeHandler());
    register(JdbcType.BIGINT, new LongTypeHandler());

    register(BigDecimal.class, new BigDecimalTypeHandler());
    register(JdbcType.REAL, new BigDecimalTypeHandler());
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

    register(InputStream.class, new BlobInputStreamTypeHandler());
    register(Byte[].class, new ByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.BLOB, new BlobTypeHandler());

    register(Object.class, UNKNOWN_TYPE_HANDLER);
    register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);

    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
    register(JdbcType.DATE, new DateOnlyTypeHandler());
    register(JdbcType.TIME, new TimeOnlyTypeHandler());

    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

    register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());

    register(Instant.class, InstantTypeHandler.class);
    register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
    register(LocalDate.class, LocalDateTypeHandler.class);
    register(LocalTime.class, LocalTimeTypeHandler.class);
    register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
    register(OffsetTime.class, OffsetTimeTypeHandler.class);
    register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
    register(Month.class, MonthTypeHandler.class);
    register(Year.class, YearTypeHandler.class);
    register(YearMonth.class, YearMonthTypeHandler.class);
    register(JapaneseDate.class, JapaneseDateTypeHandler.class);

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());

已經實現但是默認沒有註冊的handler

  • ArrayTypeHandler

  • EnumOrdinalTypeHandler

  • EnumTypeHandler

  • ObjectTypeHandler

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章