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


我們在使用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這個配置項的理解,不正確的地方希望看到文章的你能給予指正,謝了~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM