ORM框架最重要功能是將面向對象方法中的對象和關系型數據庫中的表關聯了起來,在關聯過程中就必然涉及到對象中的數據類型和數據庫中的表字段類型的轉換,Mybatis中的org.apache.ibatis.type包主要就是實現這個功能。
一、org.apache.ibatis.type的基礎類
在mybatis的官網中(http://mybatis.github.io/mybatis-3/configuration.html#typeHandlers)關於類型轉換有如下的描述
Whenever MyBatis sets a parameter on a PreparedStatement or retrieves a value from a ResultSet, a TypeHandler is used to retrieve the value in a means appropriate to the Java type.
當MyBatis為PreparedStatement 設置參數時或者從ResultSet中獲取數據時,會根據Java類型使用TypeHandler 去獲取相應的值。
官網中也列出了每一個TypeHandler用來處理對應的JDBC類型和JAVA類型。
1、TypeHandler接口
這個接口有三個方法,一個set,用來給PreparedStatement對象對應的列設置參數;兩個get,從ResultSet和CallableStatement獲取對應列的值,不同之處是一個是取第幾個位置的值,一個是取具體列名所對應的值。set用來將Java對象中的數據類型轉換為JDBC中對應的數據類型,get用來將JDBC中對應的數據類型轉換為Java對象中的數據類型轉換。
2、BaseTypeHandler抽象類
在進行軟件設計時提倡面向接口的設計,但接口只是一個接口,並不做任何實質性的操作,還需有一系列的實現才可以真正的達到目標。BaseTypeHandler類便是對TypeHandler接口的初步實現,在實現TypeHandler接口的三個函數外,又引入了3個抽象函數用於null值的處理。
3、DateTypeHandler
書接上文,BaseTypeHandler類也是一個抽象類,按照Java的規定抽象類並不能初始化,也不能直接使用,因而還需要有具體的類。在type包中有十多個具體的類來具體處理類型轉換,每一個類處理一個數據類型,像long、int、double等等,我們以一個稍微復雜些的DateTypeHandler類為例,了解下對日期是如何進行處理的。
1)setNonNullParameter
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { ps.setTimestamp(i, new java.sql.Timestamp(((Date) parameter).getTime())); }
首先將參數parameter這個Object轉換為Date類型,而后通過Date對象的getTime()將日期轉為毫秒數,而后再將毫秒數轉換為java.sql.Timestamp對象。即將java.util.Date對象轉換為java.sql.Timestamp對象。
2)getNullableResult
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { java.sql.Timestamp sqlTimestamp = rs.getTimestamp(columnName); if (sqlTimestamp != null) { return new java.util.Date(sqlTimestamp.getTime()); } return null; } public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { java.sql.Timestamp sqlTimestamp = cs.getTimestamp(columnIndex); if (sqlTimestamp != null) { return new java.util.Date(sqlTimestamp.getTime()); } return null; }
從上面的代碼可以看出這兩個函數的作用就是將 java.sql.Timestamp對象轉換為 java.util.Date對象.
4、類圖
綜合而言,type包基礎類的類圖示例如下:
二、自定義類型處理或覆蓋默認的類型處理
在mybatis的官網中(http://mybatis.github.io/mybatis-3/configuration.html#typeHandlers)關於有如下的描述:
You can override the type handlers or create your own to deal with unsupported or non-standard types. To do so, simply extend the org.apache.ibatis.type.BaseTypeHandler class and optionally map your new TypeHandler class to a JDBC type.
可以覆蓋type handler或者創建自己的type handler去處理mybatis不支持的或者非標准的數據類型。實現這個功能,只需要繼承org.apache.ibatis.type.BaseTypeHandler類然后選擇新的TypeHandler類和JDBC類型的對應關系即可。
在官網中也給出了詳細了示例,這里不再進行重復。其中用到了兩個注解:MappedJdbcTypes 和 MappedTypes,我們可以看下這兩個注解的定義文件:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MappedJdbcTypes { public JdbcType[] value(); } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MappedTypes { public Class[] value(); }
MappedJdbcTypes傳入的是JdbcType類型的(JdbcType是type包中的一個枚舉類型),MappedTypes傳入的Class類型。
在聲明完自己的類型轉換之后,還需要讓mybatis知道這些新的類型轉換類,這可以通過在配置文件中添加typeHandlers節點來實現。可以添加一個類,也可以添加一個包中所有的類。
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
<package name="org.mybatis.example"/>
</typeHandlers>
三、typeHandler的注冊:TypeHandlerRegistry
前面介紹了mybatis中已定義好的typeHandler,也介紹了如何自定義typehandler,以及如何讓mybatis知道這次自定義的typehandler。現在剩下最關鍵的步驟,在mybatis初始化后如何將這些typehandler注冊到mybatis中,在執行數據庫操作去使用這些類。這些操作是由TypeHandlerRegistry類實施的。
我們已經知道,mybatis在使用時需要一個配置文件來進行各種各樣的設置,與這個配置文件相對應的是org.apache.ibatis.session.Configuration這個類,配置文件中每一項都對應Configuration類中的一個屬性,typehand就是Configuration類中的一個屬性。
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
我們現在來看看TypeHandlerRegistry類中常用的方法及其作用。
1、TypeHandlerRegistry類中的屬性和常用方法
private static final Map<Class<?>, Class<?>> reversePrimitiveMap = new HashMap<Class<?>, Class<?>>() { { 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); } }; private final Map<JdbcType, TypeHandler> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler>(JdbcType.class); private final Map<Class<?>, Map<JdbcType, TypeHandler>> TYPE_HANDLER_MAP = new HashMap<Class<?>, Map<JdbcType, TypeHandler>>(); private final TypeHandler UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
1)reversePrimitiveMap
可以看出來,這個map就是一個java中的基本數據類型和他們對應的類一一關聯起來,像Byte和byte。在進行注冊時會用到這個屬性,詳見第三小節。
2)JDBC_TYPE_HANDLER_MAP
JdbcType和typehandler的對應關系,通過如下的函數進行維護
public void register(JdbcType jdbcType, TypeHandler handler) { JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler); }
3)TYPE_HANDLER_MAP
TYPE_HANDLER_MAP屬相是一個關鍵的屬性,java類型和jdbctype的對應關系及處理類都存在這個屬性中,從這個map的定義中可以看到多個jdbc類型能夠對應到一個java類型。通過如下的函數進行維護:
public void register(Class<?> type, JdbcType jdbcType, TypeHandler handler) { //先查看這個java類型是否已經綁定過了,如果沒有綁定過,創建了一個map,否則就直接添加新的 Map<JdbcType, TypeHandler> map = TYPE_HANDLER_MAP.get(type); if (map == null) { map = new HashMap<JdbcType, TypeHandler>(); TYPE_HANDLER_MAP.put(type, map); } map.put(jdbcType, handler); //如果當前添加的是屬於Byte、Long等類型,將其對應的基本類型也進行注冊 if (reversePrimitiveMap.containsKey(type)) { register(reversePrimitiveMap.get(type), jdbcType, handler); } }
4)注冊自定義type handler
在注冊自定義的type handler之前需要先定位到具體的類或者包,類的處理比較簡單,直接利用java的反射機制就可以知道這個類的Class屬性了。對於包就稍微復雜些,在mybatis中是利用io包ResolverUtil類中的find函數來實現的,這里不做詳細介紹,等介紹到mybatis的io包時再詳細說明。我們來看注冊包的函數:
public void register(String packageName) { // 先聲明一個ResolverUtil對象 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); // 利用find函數找到這個包里所有的類,並且放到resolverUtil 的matches屬性中 resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName); Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses(); //通過for循環依次將加載 for (Class<?> type : handlerSet) { //Ignore inner classes and interfaces (including package-info.java) and abstract classes //不處理內部類、接口、抽象類以及packgee_info類 if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { try { //利用反射機制創建一個typehandler TypeHandler handler = (TypeHandler) type.getConstructor().newInstance(); //注冊 register(handler); } catch (Exception e) { throw new RuntimeException("Unable to find a usable constructor for " + type, e); } } } }
從上面的代碼中可以看到,真正進行注冊用到的是如下的函數,先處理MappedTypes這個注解:
public void register(TypeHandler handler) { boolean mappedTypeFound = false; //判斷這個類是否有MappedTypes這個注解 MappedTypes mappedTypes = (MappedTypes) handler.getClass().getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> handledType : mappedTypes.value()) { //進行注冊並設置mappedTypeFound變量為true register(handledType, handler); mappedTypeFound = true; } } //如果mappedTypeFound為false,則拋出一個異常, // //注意,這里和官網上的示例不同,官網上的示例並沒有這個注解, //但是在源代碼中如果沒有這個注解則不會繼續下去, //因而如果是要自定義類型轉換,還是需要添加這個注解 if (!mappedTypeFound) { throw new RuntimeException("Unable to get mapped types, check @MappedTypes annotation for type handler " + handler); } }
上面的函數又調用了如下的函數,去處理MappedJdbcTypes注解:
public void register(Class<?> type, TypeHandler handler) { MappedJdbcTypes mappedJdbcTypes = (MappedJdbcTypes) handler.getClass().getAnnotation(MappedJdbcTypes.class); // 對注解進行判斷,如果有MappedJdbcTypes這個注解,則對其對應的jdbctype依次進行注冊,否則注冊為null if (mappedJdbcTypes != null) { for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { register(type, handledJdbcType, handler); } } else { register(type, null, handler); } }
最后調用第三小節中提到的函數進行注冊到map中,供程序執行時調用。
四、TypeHandlerRegistry的初始化
前面介紹了如何自定義類型轉換處理類並注冊到mybatis中,那type包中有不少mybatis已實現的類型轉換處理類,這些類是如何及在何時注冊到mybatis中的呢?
mybatis中的實現很簡單,它把這些都放在了TypeHandlerRegistry的構造函數中了。由於構造函數比較大,也沒有用到什么新方法,再此就不貼代碼了,有興趣的讀者可以自己去觀看。
五、小結
本文對Mybatis中的type轉換做了介紹,介紹了其設計結構、常用的方法、如何自定義類型處理類、如何進行注冊等內容,希望對大家理解mybatis如何實現java類型到JDBC類型轉換有所幫助。
當然,這里這是介紹了如何轉換,但是這些轉換是怎么使用的並沒有涉及,這些內容將放到mybatis的executor中進行介紹。