Java EE開發平臺隨手記3——Mybatis擴展2

  忙裏偷閒,繼續上週的話題,記錄Mybatis的擴展。java

  擴展5:設置默認的返回結果類型web

  你們知道,在Mybatis的sql-mapper配置文件中,咱們須要給<select>元素添加resultType或resultMap屬性,這兩個屬性有且只能有一個。2013年我在作一個系統的時候,由於業務關係,查詢出的結果集字段常常變化,爲了簡化處理,採用map做爲返回數據的載體,而後不得不在絕大多數<select>元素上添加相似 resultType='java.util.HashMap'(Mybatis有HashMap的簡寫形式,這裏爲了更清晰,使用全限定符),因而催生了一個想法,能不能設置默認的返回結果類型?後面通過調試,繼承SqlSessionFactoryBean添加以下代碼實現:sql

 1 /**
 2  * 設置默認的查詢結果返回類型
 3  * @param configuration
 4  * @param cls
 5  * @throws Exception 
 6  */
 7 private void setDefaultResultType(Configuration configuration, Class<?> cls) throws Exception{
 8     try {
 9         Field resultMaps = MappedStatement.class.getDeclaredField("resultMaps");
10         resultMaps.setAccessible(true);
11         for(Iterator<MappedStatement> i = configuration.getMappedStatements().iterator(); i.hasNext();){
12             Object o = i.next();
13             /**
14              * 這裏添加類型判斷,是由於Mybatis實現中還存放了Ambiguity對象(sql-id的最後一段id重複狀況下)
15              */
16             if(o instanceof MappedStatement){
17                 MappedStatement ms = (MappedStatement)o;
18                 if(SqlCommandType.SELECT.equals(ms.getSqlCommandType()) && ms.getResultMaps().isEmpty()){
19                     ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration,ms.getId()+"-Inline",cls,new ArrayList<ResultMapping>(),null);
20                     ResultMap resultMap = inlineResultMapBuilder.build();
21                     List<ResultMap> rm = new ArrayList<ResultMap>();
22                     rm.add(resultMap);
23                     resultMaps.set(ms, Collections.unmodifiableList(rm));
24                 }else{
25                 }
26             }
27         }
28     } catch (Exception e) {
29         e.printStackTrace();
30         throw e;
31     }
32 }
View Code

這個實現有不少寫死的代碼,也沒有作足夠完備的測試,不過到目前爲止,總算尚未出錯。數據庫

  我設置的默認返回結果類型是map,固然,這裏其實能夠更進一步擴展,添加一個SqlID模式和默認結果類型的映射接口,而後就根據須要去實現這個映射關係了。數組

  擴展6:自動掃描類型簡稱mybatis

  仍是在SqlSessionFactoryBean繼承類中,另外實現的一個擴展就是自動掃描類型簡稱。類型簡稱的用法以下:app

(1)在mybatis全局配置文件中添加別名webapp

1 <typeAliases>
2         <typeAlias alias="RoleBean" type="com.forms.beneform4j.webapp.systemmanage.role.bean.RoleBean" />
3     </typeAliases>
View Code

(2)在sql-mapper文件中使用alias。ide

  經過添加自動掃描類型簡稱,就能夠將第一段配置去掉,而直接使用別名了。具體實現大概以下所示:函數

 1 private void scanTypeAliases(){
 2     if (this.autoScanTypeAliases && hasLength(this.typeAliasesScanPackage) && null != baseClass) {
 3         String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesScanPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
 4         List<Class<?>> list = new ArrayList<Class<?>>();
 5         List<String> alias = new ArrayList<String>();
 6         MetaObject meta = SystemMetaObject.forObject(this);
 7         Class<?>[] orig = (Class<?>[])meta.getValue("typeAliases");
 8         if(null != orig)
 9         {
10             for(Class<?> t : orig){
11                 list.add(t);
12                 alias.add(t.getSimpleName().toLowerCase());
13             }
14         }
15         for (String packageToScan : typeAliasPackageArray) {
16             for(Class<?> type : CoreUtils.scanClassesByParentCls(packageToScan,  baseClass)){
17                 String a = type.getSimpleName().toLowerCase();
18               if (!alias.contains(a)) {
19                   list.add(type);
20                   alias.add(a);
21               }else{
22                   CommonLogger.warn("Mybatis在自動掃描註冊別名時,發現有多個可簡寫爲"+type.getSimpleName()+"的類,將取第一個類,忽略"+type);
23               }
24             }
25         }
26         super.setTypeAliases(list.toArray(new Class<?>[list.size()]));
27     }
28 }
View Code

這裏屬性autoScanTypeAliases表示是否須要自動掃描,typeAliasesScanPackage表示掃描的包,baseClass表示掃描的接口或父類。

  

  咱們使用Mybatis,能夠在父類中注入SqlSessionTemplate,而後子類調用相關方法,也能夠經過寫Dao的接口,讓mybatis自動生成動態代理類,還能夠編寫一個靜態幫助類,在這個幫助類中注入SqlSessionTemplate,而後提供相應的靜態方法。這三種方法,之前用的多的是第三種方法,而如今,由於要讓其餘同事更容易接受,模塊劃分更清晰,採用了第二種方法。但這三種方法都有一個特色,那就是隻使用Mybatis的SqlSession接口的原生方法。不能直接調用批處理、存儲過程等,因而,我在SqlSession基礎上,添加了一個IDaoTemplate接口:

  1 public interface IDaoTemplate{
  2 
  3     /**
  4      * 查詢單筆數據
  5      * @param sqlId SQL-ID
  6      * @return 單個對象
  7      */
  8     public <T> T selectOne(String sqlId);
  9 
 10     /**
 11      * 查詢單筆數據
 12      * @param sqlId     SQL-ID
 13      * @param parameter 參數對象
 14      * @return 單個對象
 15      */
 16     public <T> T selectOne(String sqlId, Object parameter);
 17 
 18     /**
 19      * 查詢列表數據
 20      * @param sqlId SQL-ID
 21      * @return 對象列表
 22      */
 23     public <E> List<E> selectList(String sqlId);
 24 
 25     /**
 26      * 查詢列表數據
 27      * @param sqlId     SQL-ID
 28      * @param parameter 參數對象
 29      * @return 對象列表
 30      */
 31     public <E> List<E> selectList(String sqlId, Object parameter);
 32     
 33     /**
 34      * 查詢分頁列表數據
 35      * @param sqlId  SQL-ID
 36      * @param page   分頁對象
 37      * @return 指定頁的對象列表
 38      */
 39     public <E> List<E> selectList(String sqlId, IPage page);
 40 
 41     /**
 42      * 查詢分頁列表數據
 43      * @param sqlId     SQL-ID
 44      * @param parameter 參數對象
 45      * @param page      分頁對象
 46      * @return 指定頁的對象列表
 47      */
 48     public <E> List<E> selectList(String sqlId, Object parameter, IPage page);
 49     
 50     /**
 51      * 流式查詢
 52      * @param sqlId SQL-ID
 53      * @return 流式操做接口
 54      */
 55     public <E>IListStreamReader<E> selectListStream(String sqlId);
 56     
 57     /**
 58      * 流式查詢
 59      * @param sqlId     SQL-ID
 60      * @param parameter 參數對象
 61      * @return 流式操做接口
 62      */
 63     public <E>IListStreamReader<E> selectListStream(String sqlId, Object parameter);
 64     
 65     /**
 66      * 流式查詢
 67      * @param sqlId      SQL-ID
 68      * @param fetchSize  每次讀取的記錄條數(0, 5000]
 69      * @return 流式操做接口
 70      */
 71     public <E>IListStreamReader<E> selectListStream(String sqlId, int fetchSize);
 72 
 73     /**
 74      * 流式查詢
 75      * @param sqlId      SQL-ID
 76      * @param parameter  參數對象
 77      * @param fetchSize  每次讀取的記錄條數(0, 5000]
 78      * @return 流式操做接口
 79      */
 80     public <E>IListStreamReader<E> selectListStream(String sqlId, Object parameter, int fetchSize);
 81     
 82     /**
 83      * 新增
 84      * @param sqlId SQL-ID
 85      * @return 影響的記錄條數
 86      */
 87     public int insert(String sqlId);
 88 
 89     /**
 90      * 新增
 91      * @param sqlId     SQL-ID
 92      * @param parameter 參數對象
 93      * @return 影響的記錄條數
 94      */
 95     public int insert(String sqlId, Object parameter);
 96 
 97     /**
 98      * 修改
 99      * @param sqlId SQL-ID
100      * @return 影響的記錄條數
101      */
102     public int update(String sqlId);
103 
104     /**
105      * 修改
106      * @param sqlId     SQL-ID
107      * @param parameter 參數對象
108      * @return 影響的記錄條數
109      */
110     public int update(String sqlId, Object parameter);
111     
112     /**
113      * 刪除
114      * @param sqlId SQL-ID
115      * @return 影響的記錄條數
116      */
117     public int delete(String sqlId);
118 
119     /**
120      * 刪除
121      * @param sqlId     SQL-ID
122      * @param parameter 參數對象
123      * @return 影響的記錄條數
124      */
125     public int delete(String sqlId, Object parameter);
126     
127     /**
128      * 執行批量:一個SQL執行屢次
129      * @param sqlId      SQL-ID
130      * @param parameters 參數對象數組
131      * @return 批量執行的影響記錄數組
132      */
133     public int[] executeBatch(String sqlId, List<?> parameters);
134     
135     /**
136      * 執行批量:一次執行多個SQL
137      * @param sqlIds  要執行的一組SQL-ID
138      * @return 批量執行的影響記錄數組
139      */
140     public int[] executeBatch(List<String> sqlIds);
141 
142     /**
143      * 執行批量:一次執行多個SQL
144      * @param sqlIds     要執行的一組SQL-ID
145      * @param parameters 參數對象數組
146      * @return 批量執行的影響記錄數組
147      */
148     public int[] executeBatch(List<String> sqlIds, List<?> parameters);
149     
150     /**
151      * 打開批量執行模式
152      */
153     public void openBatchType();
154     
155     /**
156      * 恢復打開批量執行模式以前的執行模式
157      */
158     public void resetBatchType();
159     
160     /**
161      * 獲取批量執行結果
162      * @return
163      */
164     public int[] flushBatch();
165     
166     /**
167      * 調用存儲過程
168      * @param sqlId  SQL-ID
169      * @return 存儲過程返回結果接口
170      */
171     public ICallResult call(String sqlId);
172       
173     /**
174      * 調用存儲過程
175      * @param sqlId     SQL-ID
176      * @param parameter 參數對象
177      * @return 存儲過程返回結果接口
178      */
179     public ICallResult call(String sqlId, Object parameter);
180 }
View Code

能夠看到,其中部分是簡單調用SqlSession接口,但也有部分是咱們的擴展。

  擴展7:流式查詢

  流式查詢有四個重置方法,sql-Id是必須的參數,查詢參數parameter和每次處理的記錄條數fetchSize是可選的。流式查詢的結果接口以下:

 1 public interface IListStreamReader<T> {
 2 
 3     /**
 4      * 讀取當前批次的數據列表,若是沒有數據,返回null
 5      * @return 當前批次的數據列表
 6      */
 7     public List<T> read();
 8     
 9     /**
10      * 重置讀取批次
11      */
12     public void reset();
13 }
View Code

只有兩個方法,其中關鍵方法是獲取當前批次的數據結果集,輔助方法是重置讀取批次。

  流式查詢本質上並無執行查詢,而只是將查詢須要的要素包裝成爲一個對象,當調用者調用這個對象的read方法時,才真正執行數據庫查詢,而執行查詢又使用實現內中內置的分頁對象,每次讀取只讀取當前批次(當前頁數)的結果集,查詢以後,就內置分頁對象的當前頁數指向下一頁。

  流式查詢適用於大數據量的查詢處理,好比大數據量的數據須要生成Excel文件供客戶端下載,一次性查詢很容易內存溢出,使用流式查詢就能夠很好的解決這個問題。

  把流式查詢結果對象的抽象實現貼在這裏,應該更便於理解:

 1 public abstract class AbstractListStreamReader<T> implements IListStreamReader<T>{
 2     
 3     /**
 4      * 默認的每次讀取記錄數
 5      */
 6     private static final int defaultFetchSize = 1000;
 7     
 8     /**
 9      * 最大的每次讀取記錄數
10      */
11     private static final int maxFetchSize = 5000;
12     
13     /**
14      * 實際的每次讀取記錄數
15      */
16     private final int fetchSize;
17     
18     /**
19      * 分頁對象
20      */
21     private final IPage page;
22     
23     /**
24      * 是否完成的標誌
25      */
26     private transient boolean finish = false;//是否完成
27     
28     /**
29      * 無參構造函數
30      */
31     public AbstractListStreamReader() {
32         this(defaultFetchSize);
33     }
34     
35     /**
36      * 使用指定讀取數大小的構造函數
37      * @param fetchSize 每次讀取的記錄條數
38      */
39     public AbstractListStreamReader(int fetchSize) {
40         if(fetchSize <= 0 || fetchSize > maxFetchSize){
41             Throw.throwRuntimeException(DaoExceptionCodes.BF020012, fetchSize, "(0, "+maxFetchSize+"]");
42         }
43         this.fetchSize = fetchSize;
44         BasePage page = new BasePage();
45         page.setPageSize(fetchSize);
46         this.page = page;
47     }
48     
49     /**
50      * 讀取當前批次的列表數據,讀取的時候會加鎖
51      */
52     @Override
53     public synchronized List<T> read() {
54         if(!finish){
55             List<T> rs = doRead(page);//查詢當前頁數據
56             if(page.hasNextPage()){//有下一頁,遊標指向下一頁
57                 page.setPageProperty(page.getTotalRecords(), page.getCurrentPage()+1, fetchSize);
58             }else{//沒有下一頁,完成
59                 finish = true;
60             }
61             return rs;
62         }
63         return null;
64     }
65     
66     /**
67      * 執行實際的讀取操做
68      * @param page 分頁對象
69      * @return 和分頁對象相對應的數據記錄列表
70      */
71     abstract protected List<T> doRead(IPage page);
72     
73     /**
74      * 重置讀取批次,重置過程當中會加鎖
75      */
76     @Override
77     public synchronized void reset(){
78         this.finish = false;
79         this.page.setPageProperty(this.page.getTotalPages(), 1, fetchSize);
80     }
81 }
View Code

至於具體的實現,只要繼承抽象實現,而後實現

abstract protected List<T> doRead(IPage page);

就能夠了,而這個方法只是一個簡單的分頁查詢,實現起來沒有任何難度。

  擴展8:調用存儲過程

  Mybatis中能夠調用存儲過程,但直接使用並不方便,咱們將其封裝以下:

 1 public interface IDaoTemplate{
 2 
 3     /**
 4      *  這裏省略了其它方法
 5      */
 6     
 7     
 8     /**
 9      * 調用存儲過程
10      * @param sqlId  SQL-ID
11      * @return 存儲過程返回結果接口
12      */
13     public ICallResult call(String sqlId);
14       
15     /**
16      * 調用存儲過程
17      * @param sqlId     SQL-ID
18      * @param parameter 參數對象
19      * @return 存儲過程返回結果接口
20      */
21     public ICallResult call(String sqlId, Object parameter);
22 }
View Code

這樣調用就很是方便了,那麼這裏的ICallResult是什麼呢?看一下它的定義:

 1 public interface ICallResult {
 2     
 3     /**
 4      * 獲取存儲過程返回值
 5      * @return
 6      */
 7     public <T> T getResult();
 8 
 9     /**
10      * 根據參數名稱返回輸出參數
11      * @param name 輸出參數名稱
12      * @return 和輸出參數名稱相對應的返回結果,若是不存在輸出參數,拋出平臺運行時異常
13      */
14     public <T> T getOutputParam(String name);
15     
16     /**
17      * 返回輸出參數名稱的迭代器
18      * @return 輸出參數名迭代器
19      */
20     public Iterator<String> iterator();
21 }
View Code

使用過Mybatis調用存儲過程的朋友,看了這個藉口應該就能明白,但鑑於存儲過程調用並不普通,這裏舉一個例子:

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 3 <mapper namespace="com.forms.beneform4j.core.dao.mybatis.mapper.call.ICallDao">
 4 
 5     <select id="call" statementType="CALLABLE">
 6         {call BF_TEST_PACKAGE.BF_TEST_PROCEDURE(
 7             #{input, jdbcType=VARCHAR}, 
 8             #{output1, mode=OUT, jdbcType=VARCHAR}, 
 9             #{output2, mode=OUT, jdbcType=VARCHAR}, 
10             #{rs1, mode=OUT, jdbcType=CURSOR}, 
11             #{rs2, mode=OUT, jdbcType=CURSOR}
12         )}
13     </select>
14 </mapper>
View Code

如上配置,傳入sqlId和參數對象(含input屬性)後,返回的ICallResult接口中,能夠經過以下的方式獲取存儲過程的返回值(若是有)和輸出參數:

 1 @Repository
 2 interface ICallDao {
 3 
 4     public ICallResult call(@Param("input")String input);
 5 }
 6 
 7 @Service
 8 public class ICallDaoTest {
 9 
10     @Autowired
11     private ICallDao dao;
12     
13     @Test
14     public void call() throws Exception {
15         ICallResult rs = dao.call("1");
16         //直接訪問返回結果和輸出參數
17         Object returnValue = rs.getResult();
18         Object output1 = rs.getOutputParam("output1");
19         List<Object> rs1 = rs.getOutputParam("rs1");
20         
21         //循環訪問輸出參數
22         Iterator<String> i = rs.iterator();
23         String name = "";
24         while(i.hasNext()){
25             name = i.next();
26             System.out.println(name + "============" + rs.getOutputParam(name));
27         }
28     }
29 }
View Code

   說完了調用存儲過程的用法,回過頭來簡單的提一下調用存儲過程的實現:實際上很簡單,只要添加一個Mybatis的攔截器便可,攔截結果處理接口ResultSetHandler的方法handleOutputParameters,而後將返回結果和輸出參數包裝到一個Map對象中便可,具體代碼就不貼了。

  時間關係,今天寫到這裏。下次再繼續寫Dao接口中SqlID的重定向、IDaoTemplate接口中的批量處理相關的擴展。

相關文章
相關標籤/搜索