前言
開始分析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資料庫執行過程了,還得慢慢來。如覺得還可以,還請看官們點個贊支援下。