原創作品,可以轉載,但是請標注出處地址:http://www.cnblogs.com/V1haoge/p/6709157.html
1、回顧
上一篇研究的是類型別名注冊器TypeAliasRegister,它主要用於將基本類型和用戶自定義的類型進行別名注冊,將別名及其對應類類型保存在一個HashMap中,方便存取,是映射器映射功能實現的基礎,本篇所研究的類型處理器注冊器TypeHandlerReister是用來統籌管理類型處理器的,類型處理器是真正用於進行java類型與數據庫類型映射的工具。
這一篇我們還是重點研究類型處理器的注冊器,有關具體類型處理器的研究放到之后進行。
2、類型處理器
為了研究類型處理器注冊器,我們需要對類型處理器有一定的基礎和認識,這里簡單介紹一下,具體內容可等下一篇。
類型處理器簡單點說就是用於處理javaType與jdbcType之間類型轉換用的處理器,MyBatis針對諸多Java類型與數據庫類型進行了匹配處理。
它主要用於映射器配置文件的工作,在通過類型別名注冊器獲取類型別名代表的類型之后,就可以使用獲取的類型通過類型處理器注冊器來得到其對應的JdbcType和對應的類型處理器。
由此可見每個類型處理器都針對兩個類型,一個Java類型,一個數據庫類型。而類型處理器的作用就是進行二者之間的匹配、對應、轉換。
3、類型處理器注冊器
類型處理器注冊器既能完成類型處理器的注冊功能,同時也能對類型處理器進行統籌管理,其內部定義了集合來進行類型處理器的存取,同時定義了存取方法。
1 private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class); 2 private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>(); 3 private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
以上是TypeHandlerRegister中定義的三個Map集合,這三個集合是用來保存類型處理器的注冊信息的。
第一種:JDBC_TYPE_HANDLER_MAP,這是一個枚舉Map集合,其內部是以JdbcType枚舉類中枚舉值為鍵創建的一種集合,這種集合先天存在鍵(枚舉值),它是以數據庫類型為鍵來保存類型處理器,亦即將類型處理器注冊到對應的數據庫類型上。
第二種:TYPE_HANDLER_MAP,這是一個前套Map集合,內層集合是以數據庫類型為鍵保存處理器,外層集合為以Java類型來保存對應的數據庫類型及其處理器,這個集合將三者聯系起來,是真正進行三者對應關系匹配的集合。
第三種:ALL_TYPE_HANDLERS_MAP,這個集合中保存着所有的類型處理器,是以類型處理器的類類型為鍵值保存的,它可以統籌所有的類型處理器(帶有統計的效果)。
3.1 基礎類型處理器
在創建類型處理器注冊器的時候,在其無參構造器中會進行基礎類型處理器的注冊,這些注冊包括三種方式,一種是將以JavaType為鍵的保存方式,一種是以JdbcTye為鍵的保存方式,還有一種就是以JavaType與JdbcType為鍵的保存方式,這最后一種保存方式是一種嵌套的Map集合,前面的兩種只是簡單的Map集合。
下面將該無參構造器源碼羅列:
1 //構造函數里注冊系統內置的類型處理器 2 public TypeHandlerRegistry() { 3 //以下是為多個類型注冊到同一個handler 4 register(Boolean.class, new BooleanTypeHandler()); 5 register(boolean.class, new BooleanTypeHandler()); 6 register(JdbcType.BOOLEAN, new BooleanTypeHandler()); 7 register(JdbcType.BIT, new BooleanTypeHandler()); 8 9 register(Byte.class, new ByteTypeHandler()); 10 register(byte.class, new ByteTypeHandler()); 11 register(JdbcType.TINYINT, new ByteTypeHandler()); 12 13 register(Short.class, new ShortTypeHandler()); 14 register(short.class, new ShortTypeHandler()); 15 register(JdbcType.SMALLINT, new ShortTypeHandler()); 16 17 register(Integer.class, new IntegerTypeHandler()); 18 register(int.class, new IntegerTypeHandler()); 19 register(JdbcType.INTEGER, new IntegerTypeHandler()); 20 21 register(Long.class, new LongTypeHandler()); 22 register(long.class, new LongTypeHandler()); 23 24 register(Float.class, new FloatTypeHandler()); 25 register(float.class, new FloatTypeHandler()); 26 register(JdbcType.FLOAT, new FloatTypeHandler()); 27 28 register(Double.class, new DoubleTypeHandler()); 29 register(double.class, new DoubleTypeHandler()); 30 register(JdbcType.DOUBLE, new DoubleTypeHandler()); 31 32 //以下是為同一個類型的多種變種注冊到多個不同的handler 33 register(String.class, new StringTypeHandler()); 34 register(String.class, JdbcType.CHAR, new StringTypeHandler()); 35 register(String.class, JdbcType.CLOB, new ClobTypeHandler()); 36 register(String.class, JdbcType.VARCHAR, new StringTypeHandler()); 37 register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler()); 38 register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler()); 39 register(String.class, JdbcType.NCHAR, new NStringTypeHandler()); 40 register(String.class, JdbcType.NCLOB, new NClobTypeHandler()); 41 register(JdbcType.CHAR, new StringTypeHandler()); 42 register(JdbcType.VARCHAR, new StringTypeHandler()); 43 register(JdbcType.CLOB, new ClobTypeHandler()); 44 register(JdbcType.LONGVARCHAR, new ClobTypeHandler()); 45 register(JdbcType.NVARCHAR, new NStringTypeHandler()); 46 register(JdbcType.NCHAR, new NStringTypeHandler()); 47 register(JdbcType.NCLOB, new NClobTypeHandler()); 48 49 register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler()); 50 register(JdbcType.ARRAY, new ArrayTypeHandler()); 51 52 register(BigInteger.class, new BigIntegerTypeHandler()); 53 register(JdbcType.BIGINT, new LongTypeHandler()); 54 55 register(BigDecimal.class, new BigDecimalTypeHandler()); 56 register(JdbcType.REAL, new BigDecimalTypeHandler()); 57 register(JdbcType.DECIMAL, new BigDecimalTypeHandler()); 58 register(JdbcType.NUMERIC, new BigDecimalTypeHandler()); 59 60 register(Byte[].class, new ByteObjectArrayTypeHandler()); 61 register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler()); 62 register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler()); 63 register(byte[].class, new ByteArrayTypeHandler()); 64 register(byte[].class, JdbcType.BLOB, new BlobTypeHandler()); 65 register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler()); 66 register(JdbcType.LONGVARBINARY, new BlobTypeHandler()); 67 register(JdbcType.BLOB, new BlobTypeHandler()); 68 69 register(Object.class, UNKNOWN_TYPE_HANDLER); 70 register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); 71 register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); 72 73 register(Date.class, new DateTypeHandler()); 74 register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler()); 75 register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler()); 76 register(JdbcType.TIMESTAMP, new DateTypeHandler()); 77 register(JdbcType.DATE, new DateOnlyTypeHandler()); 78 register(JdbcType.TIME, new TimeOnlyTypeHandler()); 79 80 register(java.sql.Date.class, new SqlDateTypeHandler()); 81 register(java.sql.Time.class, new SqlTimeTypeHandler()); 82 register(java.sql.Timestamp.class, new SqlTimestampTypeHandler()); 83 84 // issue #273 85 register(Character.class, new CharacterTypeHandler()); 86 register(char.class, new CharacterTypeHandler()); 87 }
通過源碼可以發現:MyBatis內置注冊的類型處理器很是全面,幾乎囊括了所有常用的類型,所以一般情況下我們直接使用內置的類型處理器進行類型處理即可。
3.1.1 針對JavaType,MyBatis內置注冊了一下類型:
序號 | javaType | JdbcType | TypeHandler | 入口 | 說明 |
1 | Boolean.class | null | BooleanTypeHandler | 入口2 | |
2 | boolean.class | null | BooleanTypeHandler | ||
3 | Byte.class | null | ByteTypeHandler | ||
4 | byte.class | null | ByteTypeHandler | ||
5 | Short.class | null | ShortTypeHandler | ||
6 | short.class | null | ShortTypeHandler | ||
7 | Integer.class | null | IntegerTypeHandler | ||
8 | int.class | null | IntegerTypeHandler | ||
9 | Long.class | null | LongTypeHandler | ||
10 | long.class | null | LongTypeHandler | ||
11 | Float.class | null | FloatTypeHandler | ||
12 | float.class | null | FloatTypeHandler | ||
13 | Double.class | null | DoubleTypeHandler | ||
14 | double.class | null | DoubleTypeHandler | ||
15 | String.class | null | StringTypeHandler | ||
16 | BigDecimal.class | null | BigDecimalTypeHandler | ||
17 | BigInteger.class | null | BigIntegerTypeHandler | ||
18 | Byte[].class | null | ByteObjectArrayTypeHandler | ||
19 | byte[].class | null | ByteArrayTypeHandler | ||
20 | Object.class | null | UNKNOWN_TYPE_HANDLER | ||
21 | Date.class | null | DateTypeHandler | ||
22 | java.sql.Date.class | null | SqlDateTypeHandler | ||
23 | java.sql.Time.class | null | SqlTimeTypeHandler | ||
24 | Character.class | null | CharacterTypeHandler | ||
25 | char.class | null | CharacterTypeHandler | ||
26 | java.sql.Timestamp.class | null | SqlTimestampTypeHandler |
3.1.2 針對JdbcType,MyBatis內置注冊了一下類型:
序號 | javaType | JdbcType | TypeHandler | 入口 | 說明 |
1 | JdbcType.BOOLEAN | BooleanTypeHandler | 入口1 | ||
2 | JdbcType.BIT | BooleanTypeHandler | |||
3 | JdbcType.TINYINT | ByteTypeHandler | |||
4 | JdbcType.SMALLINT | ShortTypeHandler | |||
5 | JdbcType.INTEGER | IntegerTypeHandler | |||
6 | JdbcType.FLOAT | FloatTypeHandler | |||
7 | JdbcType.DOUBLE | DoubleTypeHandler | |||
8 | JdbcType.CHAR | StringTypeHandler | |||
9 | JdbcType.VARCHAR | StringTypeHandler | |||
10 | JdbcType.CLOB | ClobTypeHandler | |||
11 | JdbcType.LONGVARCHAR | ClobTypeHandler | |||
12 | JdbcType.NVARCHAR | NStringTypeHandler | |||
13 | JdbcType.NCHAR | NStringTypeHandler | |||
14 | JdbcType.NCLOB | NClobTypeHandler | |||
15 | dbcType.ARRAY | ArrayTypeHandler | |||
16 | JdbcType.BIGINT | LongTypeHandler | |||
17 | JdbcType.REAL | BigDecimalTypeHandler | |||
18 | JdbcType.DECIMAL | BigDecimalTypeHandler | |||
19 | JdbcType.NUMERIC | BigDecimalTypeHandler | |||
20 | JdbcType.LONGVARBINARY | BlobTypeHandler | |||
21 | JdbcType.BLOB | BlobTypeHandler | |||
22 | JdbcType.OTHER | UNKNOWN_TYPE_HANDLER | |||
23 | JdbcType.TIMESTAMP | DateTypeHandler | |||
24 | JdbcType.DATE | DateOnlyTypeHandler | |||
25 | JdbcType.TIME | TimeOnlyTypeHandler |
3.1.3 針對JdbcType和JavaType,MyBatis內置注冊了一下類型:
序號 | JavaType | JdbcType | TypeHandler | 入口 | 說明 |
1 | Date.class | JdbcType.DATE | DateOnlyTypeHandler | 入口3 | |
2 | Date.class | JdbcType.TIME | TimeOnlyTypeHandler | ||
3 | Object.class | JdbcType.OTHER | UNKNOWN_TYPE_HANDLER | ||
4 | byte[].class | JdbcType.BLOB | BlobTypeHandler | ||
5 | byte[].class | JdbcType.LONGVARBINARY | BlobTypeHandler | ||
6 | Byte[].class | JdbcType.BLOB | BlobByteObjectArrayTypeHandler | ||
7 | Byte[].class | JdbcType.LONGVARBINARY | BlobByteObjectArrayTypeHandler | ||
8 | String.class | JdbcType.CHAR | StringTypeHandler | ||
9 | String.class | JdbcType.CLOB | ClobTypeHandler | ||
10 | String.class | JdbcType.VARCHAR | StringTypeHandler | ||
11 | String.class | JdbcType.LONGVARCHAR | ClobTypeHandler | ||
12 | String.class | JdbcType.NVARCHAR | NStringTypeHandler | ||
13 | String.class | JdbcType.NCHAR | NStringTypeHandler | ||
14 | String.class | JdbcType.NCLOB | NClobTypeHandler |
3.2 注冊入口方法
通過觀察源碼我們也可以發現這三種注冊方式,我在這里將這三種方式的register方法看做三個入口,分別起名為:入口1、入口2、入口3。
其中:
入口1:對應之前介紹的第一種集合(枚舉集合),其入口方法為:
1 //入口1 2 public void register(JdbcType jdbcType, TypeHandler<?> handler) { 3 JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler); 4 }
該入口方法用於將類型處理器注冊到對應的數據庫類型。
入口2:對應之前介紹的第二種嵌套集合(其中內層集合的鍵為null),其入口方法為:
1 //入口2 2 public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) { 3 register((Type) javaType, typeHandler); 4 }
該入口方法用於將類型處理器注冊到對應的Java類型。
入口3:對應之前介紹的第二種嵌套集合,其入口方法為:
1 //入口3 2 public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) { 3 register((Type) type, jdbcType, handler); 4 }
該入口方法用於將類型處理器與Java類型和數據庫類型聯系起來。
上述的三個入口方法均是對內而設的入口方法,也就是說是用於注冊器內部基礎類型處理器(MyBatis內置的類型處理器)注冊使用的。而MyBatis還提供了自定義類型處理器的功能,也就是說在該類中還提供了對外的自定義類型處理器注冊入口。
這么理解:對內就是該方法被類內部調用進行注冊,對外就是該方法被類外部的其他類進行調用而進行注冊,這里的其他類其實就是XMLConfigBuilder類,它在構建Configuration對象時就會調用對外的注冊方法,來將用戶自定義的類型處理器注冊到注冊器中。
對外入口1:只指定包名的情況下,這種情況一般需要配合注解@MappedTypes使用,使用該注解進行JavaType的設置(即注解的value值)
1 //對外入口1:掃描器 2 public void register(String packageName) { 3 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); 4 resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName); 5 Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses(); 6 for (Class<?> type : handlerSet) { 7 //Ignore inner classes and interfaces (including package-info.java) and abstract classes 8 if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { 9 register(type); 10 } 11 } 12 }
這個入口是一個掃描器,它會使用ResolverUtil工具類在給定包名下掃描所有類,然后進行循環注冊,注冊之前會進行排除操作,將內部類、接口、抽象類排除在外。這個掃描器針對的就是我們自定義的類型處理器進行注冊,這個入口方法會在構建Configuration配置類時由XMLConfigBuilder進行調用,用於將用戶自定義的類型處理器注冊到注冊器中。
上面的入口是在指定包名的情況下進行包掃描來獲取包下所有類來進行類型處理器注冊,一般會配合注解一起使用,但是如果不配合注解也能成功。如果配合注解指定JavaType,那么它將與對外入口2的情況一致(指定JavaType與TypeHandler),如果沒有配合注解,那么就只有TypeHandler,這時候會調用另外一個注冊方法,在這個方法中會再次驗證是否存在注解,不存在的話,那么驗證獲取的類是否是TypeReference接口的實現類,如果是其實現類,說明這個類是一個類型處理器,那么再次調用另外一個注冊方法,以該類型處理器的原生類型為參數進行調用,在這個方法中需要查詢該類型處理器是否有注解@MappedJdbcTypes來指定JdbcType,如果有則以此JdbcType值為數據庫類型,如果沒有或者是Null類型,則直接將JdbcType置null再調用核心注冊方法,將該類型處理器注冊到TYPE_HANDLER_MAP集合中,最后還有將該注冊器注冊到ALL_TYPE_HANDLERS_MAP中用於統一管理。
對外入口2:指定Java類型與類型處理器的情況
1 //對外入口2 2 public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) { 3 register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass)); 4 }
對於第二種對外入口其實在第一種的情況中已經有所描述,這個方法會調用另外一個注冊方法,來使用@MappedJdbcTypes獲取jdbcType類型,其余步驟同上。
對外入口3:指定JavaType、JdbcType、TypeHandler三者的情況
1 //對外入口3 2 public void register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass) { 3 register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass)); 4 }
這種情況可以直接調用核心注冊進行注冊即可。
對外入口4:只指定TypeHandler的情況
1 //對外入口4 2 public void register(Class<?> typeHandlerClass) { 3 boolean mappedTypeFound = false; 4 MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); 5 if (mappedTypes != null) { 6 for (Class<?> javaTypeClass : mappedTypes.value()) { 7 register(javaTypeClass, typeHandlerClass); 8 mappedTypeFound = true; 9 } 10 } 11 if (!mappedTypeFound) { 12 register(getInstance(null, typeHandlerClass)); 13 } 14 }
這種情況下,需要先驗證是否有@MappedType指定JavaType,再驗證是否有@MappedJdbcType指定JdbcType,分各種情況進行考慮,這在第一個入口方法中已經描述。
3.3 核心注冊方法
雖然擁有諸多對內對外的注冊入口方法,但是幾乎都會指向核心注冊方法,只有對內入口1不會指向核心注冊方法,因為第一種對內入口方法的執行效果是往枚舉集合JDBC_TYPE_HANDLER_MAP中注冊數據庫類型處理器。這與其他的注冊情況不同,一般我們的注冊是指往TYPE_HANDLER_MAP嵌套集合和ALL_TYPE_HANDLERS_MAP集合中注冊類型處理器。
1 private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { 2 if (javaType != null) { 3 Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType); 4 if (map == null) { 5 map = new HashMap<JdbcType, TypeHandler<?>>(); 6 TYPE_HANDLER_MAP.put(javaType, map); 7 } 8 map.put(jdbcType, handler); 9 } 10 ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler); 11 }
然我來總述一下注冊的過程,對上面的情況作何總結:
注冊就是要三者兼備,哪三者:javaType、JdbcType、TypeHandler三者兼備,針對自定義類型處理器而言,我們可以通過繼承BaseTypeHandler抽象類或者實現TypeHandler接口的方式來進行類型處理器的自定義實現。但是為了使其能在MyBatis中發揮作用,我們要將其注冊到類型處理器注冊器中。通過簡單的配置即可實現,配置方式有兩種:
1 <typeHandlers> 2 <package name="com.xx.xx"/> 3 <typeHandler handler="com.xx.xx.XxxTypeHandler" javaType="xxx" jdbcType="JdbcType.xxx" /> 4 </typeHandlers>
若是用第二種方式配置即可直接進行注冊,但是有時我們會省去javaType設置,而使用@MappedTypes注解來指定多個JavaType,或者省去JdbcType配置,采用@MappedJdbcTypes注解來指定多個jdbcType(畢竟配置文件只能指定一個,當需要設置多個時,就只能采用注解的方式實現),這時就需要查詢目標處理器類的注解來獲取類型,如果既沒有在配置文件中配置,也沒有通過注解配置,那么就只能置為null(這種情況畢竟,少見,一般我們要自定義類型處理器,必定是有某種類型處理器處理的不滿意,我們肯定會指定對應的Java類型與數據庫類型,如果聽之任之的話我們又何必多此一舉呢?)
極端情況就是采用包名配置或者只指定處理器類型進行注冊,這時需要逐步查看類型處理器類的注解配置來獲取該處理器處理的Java類型與數據庫類型,最后在雙方都獲取到的情況下,三者齊備,調用核心注冊方法,將這個類型處理器注冊到TYPE_HANDLER_MAP嵌套集合和ALL_TYPE_HANDLERS_MAP集合中。