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,查詢過程如下:
- 如果不是 Enum 類型:從當前類及其父類依次向上查找,知道找到對應的 TypeHandler
- 如果是 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;
}
備註:
- typeHandler標籤支持配置文件和註解兩種方式配置 JavaType 和 JdbcType。配置文件的優先級高於註解的優先級。推薦用註解的方式
- package 標籤只支持註解的方式配置javaType 和 JdbcType。
- 在查找 Type 的時候,如果當前沒有對應的 Map<JdbcType, TypeHandler<?>>,會繼續從父類中找,直到 Object 向上找。
- 如果 Type 爲 Enum 接口,採用深度優先遍歷查找。非 Enum 類型,採用遞歸向上直到 Object。
- @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