1. 程式人生 > >MyBatis配置文件(五)--objectFactory對象工廠

MyBatis配置文件(五)--objectFactory對象工廠

cti 將實例 什麽 hset ora interface fin 沖突 構造

我們在使用MyBatis執行查詢語句的時候,通常都會有一個返回類型,這個是在mapper文件中給sql增加一個resultType(或resultMap)屬性進行控制。resultType和resultMap都能控制返回類型,只要定義了這個配置就能自動返回我想要的結果,於是我就很納悶這個自動過程的實現原理,想必大多數人剛開始的時候應該也有和我一樣的困惑和好奇,那麽今天我就把自己的研究分享一下。在JDBC中查詢的結果會保存在一個結果集中,其實MyBatis也是這個原理,只不過MyBatis在創建結果集的時候,會使用其定義的對象工廠DefaultObjectFactory來完成對應的工作,下面來看一下其源代碼:

一、默認對象工廠DefaultObjectFactory.java

 1 /**
 2  * @author Clinton Begin
 3  */
 4 public class DefaultObjectFactory implements ObjectFactory, Serializable {
 5 
 6   private static final long serialVersionUID = -8855120656740914948L;
 7 
 8   @Override
 9   public <T> T create(Class<T> type) {
10 return create(type, null, null); 11 } 12 13 @SuppressWarnings("unchecked") 14 @Override 15 public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { 16 Class<?> classToCreate = resolveInterface(type);
17 // we know types are assignable 18 return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs); 19 } 20 21 @Override 22 public void setProperties(Properties properties) { 23 // no props for default 24 } 25 26 private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { 27 try { 28 Constructor<T> constructor; 29 if (constructorArgTypes == null || constructorArgs == null) { 30 constructor = type.getDeclaredConstructor(); 31 if (!constructor.isAccessible()) { 32 constructor.setAccessible(true); 33 } 34 return constructor.newInstance(); 35 } 36 constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()])); 37 if (!constructor.isAccessible()) { 38 constructor.setAccessible(true); 39 } 40 return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()])); 41 } catch (Exception e) { 42 StringBuilder argTypes = new StringBuilder(); 43 if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) { 44 for (Class<?> argType : constructorArgTypes) { 45 argTypes.append(argType.getSimpleName()); 46 argTypes.append(","); 47 } 48 argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing , 49 } 50 StringBuilder argValues = new StringBuilder(); 51 if (constructorArgs != null && !constructorArgs.isEmpty()) { 52 for (Object argValue : constructorArgs) { 53 argValues.append(String.valueOf(argValue)); 54 argValues.append(","); 55 } 56 argValues.deleteCharAt(argValues.length() - 1); // remove trailing , 57 } 58 throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e); 59 } 60 } 61 62 protected Class<?> resolveInterface(Class<?> type) { 63 Class<?> classToCreate; 64 if (type == List.class || type == Collection.class || type == Iterable.class) { 65 classToCreate = ArrayList.class; 66 } else if (type == Map.class) { 67 classToCreate = HashMap.class; 68 } else if (type == SortedSet.class) { // issue #510 Collections Support 69 classToCreate = TreeSet.class; 70 } else if (type == Set.class) { 71 classToCreate = HashSet.class; 72 } else { 73 classToCreate = type; 74 } 75 return classToCreate; 76 } 77 78 @Override 79 public <T> boolean isCollection(Class<T> type) { 80 return Collection.class.isAssignableFrom(type); 81 } 82 83 }

在這個類中有6個方法:

1??create(Class<T> type):這個方法我認為是創建結果集的方法,但它其實是直接調用了第二個create方法,只不過後兩個參數傳的是null,所以直接看下面的方法;

2??create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs):這個方法中傳了三個參數,分別應該是返回的結果集類型、此類構造函數參數類型、此類構造函數參數值。從它的內部實現來看,首先調用了下面的resolveInterface方法獲取返回類型,其次調用instantiateClass方法實例化出我們所需的結果集;

3??setProperties(Properties properties):這個方法是用來設置一些配置信息,比如我們給objectFactory定義一個property子元素時,就是通過這個方法進行配置的;

4??instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs):此方法是用來實例化一個類,需要實例化的類型是通過下面的resolveInterface方法決定,從內部實現來看,這個實例化過程是通過反射實現的;

5??resolveInterface(Class<?> type):此方法是用來對集合類型進行處理,即如果我們定義一個resultType為集合類型,那麽它就會根據這個類型決定出即將創建的結果集類型;

6??isCollection(Class<T> type):這個方法是用來判斷我們配置的類型是不是一個集合。比如如果返回多條數據,但是我們配置resultType是個普通類,那麽在執行過程中就會報錯;

以上就是MyBatis默認objectFactory中的具體實現,通過它來創建我們配置的結果集,一般情況下都會使用默認的對象工廠,但是我們也可以自定義一個,只要繼承DefaultObjectFactory.java即可。

二、自定義一個對象工廠MyObjectFactory.java

 1 package com.daily.objectfactory;
 2 
 3 import java.util.List;
 4 import java.util.Properties;
 5 
 6 import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
 7 
 8 public class MyObjectFactory extends DefaultObjectFactory{
 9 
10     private static final long serialVersionUID = 1L;
11     
12     @Override
13     public void setProperties(Properties properties) {
14         super.setProperties(properties);
15         System.out.println("初始化參數:["+properties.toString()+"]");
16     }
17     
18     
19     @Override
20     public <T> T create(Class<T> type,List<Class<?>> constructorArgTypes,List<Object> constructorArgs) {
21         T result = super.create(type, constructorArgTypes, constructorArgs);
22         System.out.println("創建對象方法:"+result.getClass().getSimpleName());
23         return result;
24     }
25     
26     @Override
27     public <T> boolean isCollection(Class<T> type) {
28         return super.isCollection(type);
29     }
30 
31 }

其實我們自定義的時候只要重寫其中的create方法就可以,至於選擇哪個,其實兩個都行,因為第一個方法內部還是調用了第二個,我在這裏調用了第二個,還重寫了setProperties方法,用來查看其作用。

下面據一個例子來查看怎麽使用自定義的objectFactory

第一步:配置

在mybatis-config.xml中做如下配置,並自定義一個property子元素,設置其name和value

1 <!--對象工廠 -->
2     <objectFactory type="com.daily.objectfactory.MyObjectFactory">
3         <property name="prop1" value="value1"/>
4     </objectFactory>

第二步:配置查詢映射器接口和sql

接口:

1 //獲取所有的產品
2 public List<Product> getAllProduct();

sql:

1 <select id="getAllProduct"resultMap="BaseResultMap">
2         SELECT * FROM product
3 </select>

在上面的代碼中,接口返回的是一個List,而其中的元素是Product類,sql定義返回的結果集是一個BaseResultMap,其定義如下:

1 <resultMap id="BaseResultMap" type="com.daily.pojo.Product">
2         <id column="id" jdbcType="VARCHAR" property="id" />
3         <result column="product_name" jdbcType="VARCHAR" property="productName" />
4         <result column="product_price" jdbcType="VARCHAR" property="productPrice" />
5         <result column="product_type" jdbcType="VARCHAR" property="productType" />
6 </resultMap>

它們都在同一個mapper.xml文件中.

第三步:打印查詢結果

1 // 獲取商品列表
2     public void getProductList() {
3         List<Product> productList = productService.getProductList();
4         for (Product product : productList) {
5             System.out.println(product.getProductName());
6         }
7     }

獲取到商品列表之後進行遍歷並打印名稱

第四步:查詢結果

 1 初始化參數:[{prop1=value1}]
 2 創建對象方法:ArrayList
 3 創建對象方法:Product
 4 創建對象方法:Product
 5 創建對象方法:Product
 6 創建對象方法:Product
 7 襯衫
 8 衛衣
 9 連衣裙
10 連衣裙

從執行結果來看,創建結果集的步驟如下:

1??首先進行初始化參數,也就是根據objectFactory中的配置;

2??然後創建了一個ArrayList對象;

3??再創建四個Product對象(註意:查詢結果有幾條就創建幾個 );

4??將四個對象設置到ArrayList中封裝成我們需要的返回類型List;

按照我的理解,mybatis創建結果集的過程是這樣的:首先根據sql執行的結果,即返回條數判斷是不是一個集合類型,然後將每條結果實例化成一個對象,如果前面判斷返回的是多條,則將這些對象設置到一個List中,否則就是一個單獨的對象,然後根據我們在映射器接口中定義的返回類型進行適配,如果接口定義返回List就返回List,如果接口定義返回一個單獨的類型,則返回實例化的對象。後來仔細一想不對呀,這樣的話如果查詢結果是一條,但是我在接口中定義的返回類型是List,那豈不是沖突了?為了研究這個創建過程,我又寫了兩個查詢接口,一個只返回一個對象,一個查詢所有但返回一個對象(這個按理說是不對的,主要看看mybatis會報什麽錯),然後在DefaultObjectFactory中打斷點進行調試,下面介紹我的實驗過程:

第一步:再創建兩個查詢接口

1??根據ID查詢

1 //查詢接口
2 public Product selectById(String id);

sql配置

1 <select id="selectById" parameterType="String" resultType="product">
2         SELECT * FROM product p WHERE p.id = #{id,jdbcType=VARCHAR}
3 </select>

這個是根據ID查詢,返回類型是Product

2??查詢所有

1 public Product getAllProducts();

sql配置

1 <select id="getAllProducts" resultType="product">
2         SELECT * FROM product
3 </select>

這個是查詢所有,但返回類型是Product,即一個對象

第二步:對三種情況打斷點進行調試

在調試過程中我發現以下幾點:

1、在獲取SQL session對象的過程中就已經調用了DefaultObjectFactory中的setProperties方法,其實這個不難理解,畢竟在獲取SQL session之前就已經加載了mybatis的配置文件,首先當然要進行配置;

2、然後會進入到DefaultObjectFactory中的isCollection方法,而傳給它的參數類型就是我在映射器接口中定義的返回類型;

3、不管查詢出多少條數據,也不管返回類型是什麽,首先都會調用create方法創建一個ArrayList對象;

4、查詢出多少條數據,就會調用多少次create方法,將一條數據封裝成一個類並進行實例化;

以上是三種不同的查詢結果都會執行的步驟,其他的中間還有很復雜的創建過程,根據我有限的理解,推斷出mybatis在創建結果集的過程如下:

1??先判斷接口返回的類型是一個單獨的類還是一個集合;

2??創建一個ArrayList對象;

3??將每個查詢結果實例化成一個對象,然後設置到ArrayList對象中;

4??如果在第一步中接口返回的是單獨的類,此時查看第三步封裝的結果,如果只有一條數據,則獲取對象並返回;如果有多條,則報錯;

如果在第一步中接口返回的是一個集合,則直接返回第三步封裝的結果;

其實所謂結果集,必定是個集合,所以才會有創建ArrayList並將實例化的對象添加在其中這一步,至於怎麽返回就要看接口中的定義了,如果mybatis能根據接口定義返回指定類型,則沒有任何問題,否則就會報錯,就像上面的第三種情況,封裝的結果集是個含多個product對象的集合,但是接口中定義只要一個對象,那這時候mybatis就不知道給哪個合適,所以只能報錯了。

以上就是我對objectFactory這個配置項的理解,不正確的地方希望看到文章的你能給予指正,謝了~~

MyBatis配置文件(五)--objectFactory對象工廠