相關文章java
前言數據庫
類型處理器數組
類型註冊器app
別名註冊器ide
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 等,還有三個類型的註冊類 TypeHandlerRegistry,SimpleTypeRegistry 和 TypeAliasRegistry ,此外該包下還定義了一些註解等。.net
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 是一個抽象類,它表示引用一個泛型類型:
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; } }
在 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; }
在上面的 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 獲取結果,其餘的實現類大概都如此。
在 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 的一個實現過程。
在編寫 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 進行類型轉換的一個主要代碼邏輯,仍是挺好理解的。