前言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數據庫執行過程了,還得慢慢來。如以爲還能夠,還請看官們點個贊支持下。