Mybatis攔截器介紹及分頁插件

Mybatis攔截器介紹及分頁插件

1.1    目錄

1.1 目錄

1.2 前言

1.3 Interceptor接口

1.4 註冊攔截器

1.5 Mybatis可攔截的方法

1.6 利用攔截器進行分頁

1.2     前言

       攔截器的一個作用就是我們可以攔截某些方法的調用,我們可以選擇在這些被攔截的方法執行前後加上某些邏輯,也可以在執行這些被攔截的方法時執行自己的邏輯而不再執行被攔截的方法。Mybatis攔截器設計的一個初衷就是爲了供用戶在某些時候可以實現自己的邏輯而不必去動Mybatis固有的邏輯。打個比方,對於Executor,Mybatis中有幾種實現:BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor。這個時候如果你覺得這幾種實現對於Executor接口的query方法都不能滿足你的要求,那怎麼辦呢?是要去改源碼嗎?當然不。我們可以建立一個Mybatis攔截器用於攔截Executor接口的query方法,在攔截之後實現自己的query方法邏輯,之後可以選擇是否繼續執行原來的query方法。

1.3     Interceptor接口

       對於攔截器Mybatis爲我們提供了一個Interceptor接口,通過實現該接口就可以定義我們自己的攔截器。我們先來看一下這個接口的定義:

Java代碼  收藏代碼
  1. package org.apache.ibatis.plugin;  
  2.    
  3. import java.util.Properties;  
  4.    
  5. public interface Interceptor {  
  6.    
  7.   Object intercept(Invocation invocation) throws Throwable;  
  8.    
  9.   Object plugin(Object target);  
  10.    
  11.   void setProperties(Properties properties);  
  12.    
  13. }  

 

       我們可以看到在該接口中一共定義有三個方法,intercept、plugin和setProperties。plugin方法是攔截器用於封裝目標對象的,通過該方法我們可以返回目標對象本身,也可以返回一個它的代理。當返回的是代理的時候我們可以對其中的方法進行攔截來調用intercept方法,當然也可以調用其他方法,這點將在後文講解。setProperties方法是用於在Mybatis配置文件中指定一些屬性的。

       定義自己的Interceptor最重要的是要實現plugin方法和intercept方法,在plugin方法中我們可以決定是否要進行攔截進而決定要返回一個什麼樣的目標對象。而intercept方法就是要進行攔截的時候要執行的方法。

       對於plugin方法而言,其實Mybatis已經爲我們提供了一個實現。Mybatis中有一個叫做Plugin的類,裏面有一個靜態方法wrap(Object target,Interceptor interceptor),通過該方法可以決定要返回的對象是目標對象還是對應的代理。這裏我們先來看一下Plugin的源碼:

Java代碼  收藏代碼
  1. package org.apache.ibatis.plugin;  
  2.    
  3. import java.lang.reflect.InvocationHandler;  
  4. import java.lang.reflect.Method;  
  5. import java.lang.reflect.Proxy;  
  6. import java.util.HashMap;  
  7. import java.util.HashSet;  
  8. import java.util.Map;  
  9. import java.util.Set;  
  10.    
  11. import org.apache.ibatis.reflection.ExceptionUtil;  
  12.    
  13. public class Plugin implements InvocationHandler {  
  14.    
  15.   private Object target;  
  16.   private Interceptor interceptor;  
  17.   private Map<Class<?>, Set<Method>> signatureMap;  
  18.    
  19.   private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {  
  20.     this.target = target;  
  21.     this.interceptor = interceptor;  
  22.     this.signatureMap = signatureMap;  
  23.   }  
  24.    
  25.   public static Object wrap(Object target, Interceptor interceptor) {  
  26.     Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);  
  27.     Class<?> type = target.getClass();  
  28.     Class<?>[] interfaces = getAllInterfaces(type, signatureMap);  
  29.     if (interfaces.length > 0) {  
  30.       return Proxy.newProxyInstance(  
  31.           type.getClassLoader(),  
  32.           interfaces,  
  33.           new Plugin(target, interceptor, signatureMap));  
  34.     }  
  35.     return target;  
  36.   }  
  37.    
  38.   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  39.     try {  
  40.       Set<Method> methods = signatureMap.get(method.getDeclaringClass());  
  41.       if (methods != null && methods.contains(method)) {  
  42.         return interceptor.intercept(new Invocation(target, method, args));  
  43.       }  
  44.       return method.invoke(target, args);  
  45.     } catch (Exception e) {  
  46.       throw ExceptionUtil.unwrapThrowable(e);  
  47.     }  
  48.   }  
  49.    
  50.   private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {  
  51.     Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);  
  52.     if (interceptsAnnotation == null) { // issue #251  
  53.       throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());       
  54.     }  
  55.     Signature[] sigs = interceptsAnnotation.value();  
  56.     Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();  
  57.     for (Signature sig : sigs) {  
  58.       Set<Method> methods = signatureMap.get(sig.type());  
  59.       if (methods == null) {  
  60.         methods = new HashSet<Method>();  
  61.         signatureMap.put(sig.type(), methods);  
  62.       }  
  63.       try {  
  64.         Method method = sig.type().getMethod(sig.method(), sig.args());  
  65.         methods.add(method);  
  66.       } catch (NoSuchMethodException e) {  
  67.         throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);  
  68.       }  
  69.     }  
  70.     return signatureMap;  
  71.   }  
  72.    
  73.   private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {  
  74.     Set<Class<?>> interfaces = new HashSet<Class<?>>();  
  75.     while (type != null) {  
  76.       for (Class<?> c : type.getInterfaces()) {  
  77.         if (signatureMap.containsKey(c)) {  
  78.           interfaces.add(c);  
  79.         }  
  80.       }  
  81.       type = type.getSuperclass();  
  82.     }  
  83.     return interfaces.toArray(new Class<?>[interfaces.size()]);  
  84.   }  
  85.    
  86. }  

 

       我們先看一下Plugin的wrap方法,它根據當前的Interceptor上面的註解定義哪些接口需要攔截,然後判斷當前目標對象是否有實現對應需要攔截的接口,如果沒有則返回目標對象本身,如果有則返回一個代理對象。而這個代理對象的InvocationHandler正是一個Plugin。所以當目標對象在執行接口方法時,如果是通過代理對象執行的,則會調用對應InvocationHandler的invoke方法,也就是Plugin的invoke方法。所以接着我們來看一下該invoke方法的內容。這裏invoke方法的邏輯是:如果當前執行的方法是定義好的需要攔截的方法,則把目標對象、要執行的方法以及方法參數封裝成一個Invocation對象,再把封裝好的Invocation作爲參數傳遞給當前攔截器的intercept方法。如果不需要攔截,則直接調用當前的方法。Invocation中定義了定義了一個proceed方法,其邏輯就是調用當前方法,所以如果在intercept中需要繼續調用當前方法的話可以調用invocation的procced方法。

       這就是Mybatis中實現Interceptor攔截的一個思想,如果用戶覺得這個思想有問題或者不能完全滿足你的要求的話可以通過實現自己的Plugin來決定什麼時候需要代理什麼時候需要攔截。以下講解的內容都是基於Mybatis的默認實現即通過Plugin來管理Interceptor來講解的。

       對於實現自己的Interceptor而言有兩個很重要的註解,一個是@Intercepts,其值是一個@Signature數組。@Intercepts用於表明當前的對象是一個Interceptor,而@Signature則表明要攔截的接口、方法以及對應的參數類型。來看一個自定義的簡單Interceptor:

Java代碼  收藏代碼
  1. package com.tiantian.mybatis.interceptor;  
  2.    
  3. import java.sql.Connection;  
  4. import java.util.Properties;  
  5.    
  6. import org.apache.ibatis.executor.Executor;  
  7. import org.apache.ibatis.executor.statement.StatementHandler;  
  8. import org.apache.ibatis.mapping.MappedStatement;  
  9. import org.apache.ibatis.plugin.Interceptor;  
  10. import org.apache.ibatis.plugin.Intercepts;  
  11. import org.apache.ibatis.plugin.Invocation;  
  12. import org.apache.ibatis.plugin.Plugin;  
  13. import org.apache.ibatis.plugin.Signature;  
  14. import org.apache.ibatis.session.ResultHandler;  
  15. import org.apache.ibatis.session.RowBounds;  
  16.    
  17. @Intercepts( {  
  18.        @Signature(method = "query", type = Executor.class, args = {  
  19.               MappedStatement.class, Object.class, RowBounds.class,  
  20.               ResultHandler.class }),  
  21.        @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })  
  22. public class MyInterceptor implements Interceptor {  
  23.    
  24.     public Object intercept(Invocation invocation) throws Throwable {  
  25.        Object result = invocation.proceed();  
  26.        System.out.println("Invocation.proceed()");  
  27.        return result;  
  28.     }  
  29.    
  30.     public Object plugin(Object target) {  
  31.        return Plugin.wrap(target, this);  
  32.     }  
  33.    
  34.     public void setProperties(Properties properties) {  
  35.        String prop1 = properties.getProperty("prop1");  
  36.        String prop2 = properties.getProperty("prop2");  
  37.        System.out.println(prop1 + "------" + prop2);  
  38.     }  
  39.    
  40. }  

 

       首先看setProperties方法,這個方法在Configuration初始化當前的Interceptor時就會執行,這裏只是簡單的取兩個屬性進行打印。

       其次看plugin方法中我們是用的Plugin的邏輯來實現Mybatis的邏輯的。

       接着看MyInterceptor類上我們用@Intercepts標記了這是一個Interceptor,然後在@Intercepts中定義了兩個@Signature,即兩個攔截點。第一個@Signature我們定義了該Interceptor將攔截Executor接口中參數類型爲MappedStatement、Object、RowBounds和ResultHandler的query方法;第二個@Signature我們定義了該Interceptor將攔截StatementHandler中參數類型爲Connection的prepare方法。

       最後再來看一下intercept方法,這裏我們只是簡單的打印了一句話,然後調用invocation的proceed方法,使當前方法正常的調用。

       對於這個攔截器,Mybatis在註冊該攔截器的時候就會利用定義好的n個property作爲參數調用該攔截器的setProperties方法。之後在新建可攔截對象的時候會調用該攔截器的plugin方法來決定是返回目標對象本身還是代理對象。對於這個攔截器而言,當Mybatis是要Executor或StatementHandler對象的時候就會返回一個代理對象,其他都是原目標對象本身。然後當Executor代理對象在執行參數類型爲MappedStatement、Object、RowBounds和ResultHandler的query方法或StatementHandler代理對象在執行參數類型爲Connection的prepare方法時就會觸發當前的攔截器的intercept方法進行攔截,而執行這兩個接口對象的其他方法時都只是做一個簡單的代理。

1.4    註冊攔截器

       註冊攔截器是通過在Mybatis配置文件中plugins元素下的plugin元素來進行的。一個plugin對應着一個攔截器,在plugin元素下面我們可以指定若干個property子元素。Mybatis在註冊定義的攔截器時會先把對應攔截器下面的所有property通過Interceptor的setProperties方法注入給對應的攔截器。所以,我們可以這樣來註冊我們在前面定義的MyInterceptor:

 

Xml代碼  收藏代碼
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <!DOCTYPE configuration  
  3.   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
  4.   "http://mybatis.org/dtd/mybatis-3-config.dtd">  
  5. <configuration>  
  6.     <properties resource="config/jdbc.properties"></properties>  
  7.     <typeAliases>  
  8.        <package name="com.tiantian.mybatis.model"/>  
  9.     </typeAliases>  
  10.     <plugins>  
  11.        <plugin interceptor="com.tiantian.mybatis.interceptor.MyInterceptor">  
  12.            <property name="prop1" value="prop1"/>  
  13.            <property name="prop2" value="prop2"/>  
  14.        </plugin>  
  15.     </plugins>  
  16.     <environments default="development">  
  17.        <environment id="development">  
  18.            <transactionManager type="JDBC" />  
  19.            <dataSource type="POOLED">  
  20.               <property name="driver" value="${jdbc.driver}" />  
  21.               <property name="url" value="${jdbc.url}" />  
  22.               <property name="username" value="${jdbc.username}" />  
  23.               <property name="password" value="${jdbc.password}" />  
  24.            </dataSource>  
  25.        </environment>  
  26.     </environments>  
  27.     <mappers>  
  28.        <mapper resource="com/tiantian/mybatis/mapper/UserMapper.xml"/>  
  29.     </mappers>  
  30. </configuration>  

 

1.5    Mybatis可攔截的方法

       Mybatis攔截器只能攔截四種類型的接口:Executor、StatementHandler、ParameterHandler和ResultSetHandler。這是在Mybatis的Configuration中寫死了的,如果要支持攔截其他接口就需要我們重寫Mybatis的Configuration。Mybatis可以對這四個接口中所有的方法進行攔截。

1.6     利用攔截器進行分頁

       下面將介紹一個Mybatis攔截器的實際應用。Mybatis攔截器常常會被用來進行分頁處理。我們知道要利用JDBC對數據庫進行操作就必須要有一個對應的Statement對象,Mybatis在執行Sql語句前也會產生一個包含Sql語句的Statement對象,而且對應的Sql語句是在Statement之前產生的,所以我們就可以在它成Statement之前對用來生成Statement的Sql語句下手。在Mybatis中Statement語句是通過RoutingStatementHandler對象的prepare方法生成的。所以利用攔截器實現Mybatis分頁的一個思路就是攔截StatementHandler接口的prepare方法,然後在攔截器方法中把Sql語句改成對應的分頁查詢Sql語句,之後再調用StatementHandler對象的prepare方法,即調用invocation.proceed()。更改Sql語句這個看起來很簡單,而事實上來說的話就沒那麼直觀,因爲包括sql等其他屬性在內的多個屬性都沒有對應的方法可以直接取到,它們對外部都是封閉的,是對象的私有屬性,所以這裏就需要引入反射機制來獲取或者更改對象的私有屬性的值了。對於分頁而言,在攔截器裏面我們常常還需要做的一個操作就是統計滿足當前條件的記錄一共有多少,這是通過獲取到了原始的Sql語句後,把它改爲對應的統計語句再利用Mybatis封裝好的參數和設置參數的功能把Sql語句中的參數進行替換,之後再執行查詢記錄數的Sql語句進行總記錄數的統計。先來看一個我們對分頁操作封裝的一個實體類Page:

Java代碼  收藏代碼
  1. import java.util.HashMap;  
  2. import java.util.List;  
  3. import java.util.Map;  
  4.    
  5. /** 
  6.  * 對分頁的基本數據進行一個簡單的封裝 
  7.  */  
  8. public class Page<T> {  
  9.    
  10.     private int pageNo = 1;//頁碼,默認是第一頁  
  11.     private int pageSize = 15;//每頁顯示的記錄數,默認是15  
  12.     private int totalRecord;//總記錄數  
  13.     private int totalPage;//總頁數  
  14.     private List<T> results;//對應的當前頁記錄  
  15.     private Map<String, Object> params = new HashMap<String, Object>();//其他的參數我們把它分裝成一個Map對象  
  16.    
  17.     public int getPageNo() {  
  18.        return pageNo;  
  19.     }  
  20.    
  21.     public void setPageNo(int pageNo) {  
  22.        this.pageNo = pageNo;  
  23.     }  
  24.    
  25.     public int getPageSize() {  
  26.        return pageSize;  
  27.     }  
  28.    
  29.     public void setPageSize(int pageSize) {  
  30.        this.pageSize = pageSize;  
  31.     }  
  32.    
  33.     public int getTotalRecord() {  
  34.        return totalRecord;  
  35.     }  
  36.    
  37.     public void setTotalRecord(int totalRecord) {  
  38.        this.totalRecord = totalRecord;  
  39.        //在設置總頁數的時候計算出對應的總頁數,在下面的三目運算中加法擁有更高的優先級,所以最後可以不加括號。  
  40.        int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1;  
  41.        this.setTotalPage(totalPage);  
  42.     }  
  43.    
  44.     public int getTotalPage() {  
  45.        return totalPage;  
  46.     }  
  47.    
  48.     public void setTotalPage(int totalPage) {  
  49.        this.totalPage = totalPage;  
  50.     }  
  51.    
  52.     public List<T> getResults() {  
  53.        return results;  
  54.     }  
  55.    
  56.     public void setResults(List<T> results) {  
  57.        this.results = results;  
  58.     }  
  59.      
  60.     public Map<String, Object> getParams() {  
  61.        return params;  
  62.     }  
  63.      
  64.     public void setParams(Map<String, Object> params) {  
  65.        this.params = params;  
  66.     }  
  67.    
  68.     @Override  
  69.     public String toString() {  
  70.        StringBuilder builder = new StringBuilder();  
  71.        builder.append("Page [pageNo=").append(pageNo).append(", pageSize=")  
  72.               .append(pageSize).append(", results=").append(results).append(  
  73.                      ", totalPage=").append(totalPage).append(  
  74.                      ", totalRecord=").append(totalRecord).append("]");  
  75.        return builder.toString();  
  76.     }  
  77.    
  78. }  

 

       對於需要進行分頁的Mapper映射,我們會給它傳一個Page對象作爲參數,我們可以看到Page對象裏面包括了一些分頁的基本信息,這些信息我們可以在攔截器裏面用到,然後我們把除分頁的基本信息以外的其他參數用一個Map對象進行包裝,這樣在Mapper映射語句中的其他參數就可以從Map中取值了。接着來看一下我們的PageInterceptor的定義,對於PageInterceptor我就不做過多的說明,代碼裏面附有很詳細的註釋信息:

Java代碼  收藏代碼
  1. package com.tiantian.mybatis.interceptor;  
  2.    
  3. import java.lang.reflect.Field;  
  4. import java.sql.Connection;  
  5. import java.sql.PreparedStatement;  
  6. import java.sql.ResultSet;  
  7. import java.sql.SQLException;  
  8. import java.util.List;  
  9. import java.util.Properties;  
  10.    
  11. import org.apache.ibatis.executor.parameter.ParameterHandler;  
  12. import org.apache.ibatis.executor.statement.RoutingStatementHandler;  
  13. import org.apache.ibatis.executor.statement.StatementHandler;  
  14. import org.apache.ibatis.mapping.BoundSql;  
  15. import org.apache.ibatis.mapping.MappedStatement;  
  16. import org.apache.ibatis.mapping.ParameterMapping;  
  17. import org.apache.ibatis.plugin.Interceptor;  
  18. import org.apache.ibatis.plugin.Intercepts;  
  19. import org.apache.ibatis.plugin.Invocation;  
  20. import org.apache.ibatis.plugin.Plugin;  
  21. import org.apache.ibatis.plugin.Signature;  
  22. import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;  
  23.    
  24. import com.tiantian.mybatis.model.Page;  
  25.    
  26. /** 
  27.  * 
  28.  * 分頁攔截器,用於攔截需要進行分頁查詢的操作,然後對其進行分頁處理。 
  29.  * 利用攔截器實現Mybatis分頁的原理: 
  30.  * 要利用JDBC對數據庫進行操作就必須要有一個對應的Statement對象,Mybatis在執行Sql語句前就會產生一個包含Sql語句的Statement對象,而且對應的Sql語句 
  31.  * 是在Statement之前產生的,所以我們就可以在它生成Statement之前對用來生成Statement的Sql語句下手。在Mybatis中Statement語句是通過RoutingStatementHandler對象的 
  32.  * prepare方法生成的。所以利用攔截器實現Mybatis分頁的一個思路就是攔截StatementHandler接口的prepare方法,然後在攔截器方法中把Sql語句改成對應的分頁查詢Sql語句,之後再調用 
  33.  * StatementHandler對象的prepare方法,即調用invocation.proceed()。 
  34.  * 對於分頁而言,在攔截器裏面我們還需要做的一個操作就是統計滿足當前條件的記錄一共有多少,這是通過獲取到了原始的Sql語句後,把它改爲對應的統計語句再利用Mybatis封裝好的參數和設 
  35.  * 置參數的功能把Sql語句中的參數進行替換,之後再執行查詢記錄數的Sql語句進行總記錄數的統計。 
  36.  * 
  37.  */  
  38. @Intercepts( {  
  39.        @Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class}) })  
  40. public class PageInterceptor implements Interceptor {  
  41.    
  42.     private String databaseType;//數據庫類型,不同的數據庫有不同的分頁方法  
  43.      
  44.     /** 
  45.      * 攔截後要執行的方法 
  46.      */  
  47.     public Object intercept(Invocation invocation) throws Throwable {  
  48.        //對於StatementHandler其實只有兩個實現類,一個是RoutingStatementHandler,另一個是抽象類BaseStatementHandler,  
  49.        //BaseStatementHandler有三個子類,分別是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler,  
  50.        //SimpleStatementHandler是用於處理Statement的,PreparedStatementHandler是處理PreparedStatement的,而CallableStatementHandler是  
  51.        //處理CallableStatement的。Mybatis在進行Sql語句處理的時候都是建立的RoutingStatementHandler,而在RoutingStatementHandler裏面擁有一個  
  52.        //StatementHandler類型的delegate屬性,RoutingStatementHandler會依據Statement的不同建立對應的BaseStatementHandler,即SimpleStatementHandler、  
  53.        //PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler裏面所有StatementHandler接口方法的實現都是調用的delegate對應的方法。  
  54.        //我們在PageInterceptor類上已經用@Signature標記了該Interceptor只攔截StatementHandler接口的prepare方法,又因爲Mybatis只有在建立RoutingStatementHandler的時候  
  55.        //是通過Interceptor的plugin方法進行包裹的,所以我們這裏攔截到的目標對象肯定是RoutingStatementHandler對象。  
  56.        RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();  
  57.        //通過反射獲取到當前RoutingStatementHandler對象的delegate屬性  
  58.        StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");  
  59.        //獲取到當前StatementHandler的 boundSql,這裏不管是調用handler.getBoundSql()還是直接調用delegate.getBoundSql()結果是一樣的,因爲之前已經說過了  
  60.        //RoutingStatementHandler實現的所有StatementHandler接口方法裏面都是調用的delegate對應的方法。  
  61.        BoundSql boundSql = delegate.getBoundSql();  
  62.        //拿到當前綁定Sql的參數對象,就是我們在調用對應的Mapper映射語句時所傳入的參數對象  
  63.        Object obj = boundSql.getParameterObject();  
  64.        //這裏我們簡單的通過傳入的是Page對象就認定它是需要進行分頁操作的。  
  65.        if (obj instanceof Page<?>) {  
  66.            Page<?> page = (Page<?>) obj;  
  67.            //通過反射獲取delegate父類BaseStatementHandler的mappedStatement屬性  
  68.            MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement");  
  69.            //攔截到的prepare方法參數是一個Connection對象  
  70.            Connection connection = (Connection)invocation.getArgs()[0];  
  71.            //獲取當前要執行的Sql語句,也就是我們直接在Mapper映射語句中寫的Sql語句  
  72.            String sql = boundSql.getSql();  
  73.            //給當前的page參數對象設置總記錄數  
  74.            this.setTotalRecord(page,  
  75.                   mappedStatement, connection);  
  76.            //獲取分頁Sql語句  
  77.            String pageSql = this.getPageSql(page, sql);  
  78.            //利用反射設置當前BoundSql對應的sql屬性爲我們建立好的分頁Sql語句  
  79.            ReflectUtil.setFieldValue(boundSql, "sql", pageSql);  
  80.        }  
  81.        return invocation.proceed();  
  82.     }  
  83.    
  84.    
  85.     /** 
  86.      * 攔截器對應的封裝原始對象的方法 
  87.      */  
  88.     public Object plugin(Object target) {  
  89.        return Plugin.wrap(target, this);  
  90.     }  
  91.    
  92.     /** 
  93.      * 設置註冊攔截器時設定的屬性 
  94.      */  
  95.     public void setProperties(Properties properties) {  
  96.        this.databaseType = properties.getProperty("databaseType");  
  97.     }  
  98.      
  99.     /** 
  100.      * 根據page對象獲取對應的分頁查詢Sql語句,這裏只做了兩種數據庫類型,Mysql和Oracle 
  101.      * 其它的數據庫都 沒有進行分頁 
  102.      * 
  103.      * @param page 分頁對象 
  104.      * @param sql 原sql語句 
  105.      * @return 
  106.      */  
  107.     private String getPageSql(Page<?> page, String sql) {  
  108.        StringBuffer sqlBuffer = new StringBuffer(sql);  
  109.        if ("mysql".equalsIgnoreCase(databaseType)) {  
  110.            return getMysqlPageSql(page, sqlBuffer);  
  111.        } else if ("oracle".equalsIgnoreCase(databaseType)) {  
  112.            return getOraclePageSql(page, sqlBuffer);  
  113.        }  
  114.        return sqlBuffer.toString();  
  115.     }  
  116.      
  117.     /** 
  118.      * 獲取Mysql數據庫的分頁查詢語句 
  119.      * @param page 分頁對象 
  120.      * @param sqlBuffer 包含原sql語句的StringBuffer對象 
  121.      * @return Mysql數據庫分頁語句 
  122.      */  
  123.     private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) {  
  124.        //計算第一條記錄的位置,Mysql中記錄的位置是從0開始的。  
  125.        int offset = (page.getPageNo() - 1) * page.getPageSize();  
  126.        sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());  
  127.        return sqlBuffer.toString();  
  128.     }  
  129.      
  130.     /** 
  131.      * 獲取Oracle數據庫的分頁查詢語句 
  132.      * @param page 分頁對象 
  133.      * @param sqlBuffer 包含原sql語句的StringBuffer對象 
  134.      * @return Oracle數據庫的分頁查詢語句 
  135.      */  
  136.     private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) {  
  137.        //計算第一條記錄的位置,Oracle分頁是通過rownum進行的,而rownum是從1開始的  
  138.        int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;  
  139.        sqlBuffer.insert(0"select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getPageSize());  
  140.        sqlBuffer.insert(0"select * from (").append(") where r >= ").append(offset);  
  141.        //上面的Sql語句拼接之後大概是這個樣子:  
  142.        //select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16  
  143.        return sqlBuffer.toString();  
  144.     }  
  145.      
  146.     /** 
  147.      * 給當前的參數對象page設置總記錄數 
  148.      * 
  149.      * @param page Mapper映射語句對應的參數對象 
  150.      * @param mappedStatement Mapper映射語句 
  151.      * @param connection 當前的數據庫連接 
  152.      */  
  153.     private void setTotalRecord(Page<?> page,  
  154.            MappedStatement mappedStatement, Connection connection) {  
  155.        //獲取對應的BoundSql,這個BoundSql其實跟我們利用StatementHandler獲取到的BoundSql是同一個對象。  
  156.        //delegate裏面的boundSql也是通過mappedStatement.getBoundSql(paramObj)方法獲取到的。  
  157.        BoundSql boundSql = mappedStatement.getBoundSql(page);  
  158.        //獲取到我們自己寫在Mapper映射語句中對應的Sql語句  
  159.        String sql = boundSql.getSql();  
  160.        //通過查詢Sql語句獲取到對應的計算總記錄數的sql語句  
  161.        String countSql = this.getCountSql(sql);  
  162.        //通過BoundSql獲取對應的參數映射  
  163.        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  
  164.        //利用Configuration、查詢記錄數的Sql語句countSql、參數映射關係parameterMappings和參數對象page建立查詢記錄數對應的BoundSql對象。  
  165.        BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);  
  166.        //通過mappedStatement、參數對象page和BoundSql對象countBoundSql建立一個用於設定參數的ParameterHandler對象  
  167.        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);  
  168.        //通過connection建立一個countSql對應的PreparedStatement對象。  
  169.        PreparedStatement pstmt = null;  
  170.        ResultSet rs = null;  
  171.        try {  
  172.            pstmt = connection.prepareStatement(countSql);  
  173.            //通過parameterHandler給PreparedStatement對象設置參數  
  174.            parameterHandler.setParameters(pstmt);  
  175.            //之後就是執行獲取總記錄數的Sql語句和獲取結果了。  
  176.            rs = pstmt.executeQuery();  
  177.            if (rs.next()) {  
  178.               int totalRecord = rs.getInt(1);  
  179.               //給當前的參數page對象設置總記錄數  
  180.               page.setTotalRecord(totalRecord);  
  181.            }  
  182.        } catch (SQLException e) {  
  183.            e.printStackTrace();  
  184.        } finally {  
  185.            try {  
  186.               if (rs != null)  
  187.                   rs.close();  
  188.                if (pstmt != null)  
  189.                   pstmt.close();  
  190.            } catch (SQLException e) {  
  191.               e.printStackTrace();  
  192.            }  
  193.        }  
  194.     }  
  195.      
  196.     /** 
  197.      * 根據原Sql語句獲取對應的查詢總記錄數的Sql語句 
  198.      * @param sql 
  199.      * @return 
  200.      */  
  201.     private String getCountSql(String sql) {  
  202.          
  203.        return "select count(1) from (" + sql + ")";  
  204.     }  
  205.      
  206.     /** 
  207.      * 利用反射進行操作的一個工具類 
  208.      * 
  209.      */  
  210.     private static class ReflectUtil {  
  211.        /** 
  212.         * 利用反射獲取指定對象的指定屬性 
  213.         * @param obj 目標對象 
  214.         * @param fieldName 目標屬性 
  215.         * @return 目標屬性的值 
  216.         */  
  217.        public static Object getFieldValue(Object obj, String fieldName) {  
  218.            Object result = null;  
  219.            Field field = ReflectUtil.getField(obj, fieldName);  
  220.            if (field != null) {  
  221.               field.setAccessible(true);  
  222.               try {  
  223.                   result = field.get(obj);  
  224.               } catch (IllegalArgumentException e) {  
  225.                   // TODO Auto-generated catch block  
  226.                   e.printStackTrace();  
  227.               } catch (IllegalAccessException e) {  
  228.                   // TODO Auto-generated catch block  
  229.                   e.printStackTrace();  
  230.               }  
  231.            }  
  232.            return result;  
  233.        }  
  234.         
  235.        /** 
  236.         * 利用反射獲取指定對象裏面的指定屬性 
  237.         * @param obj 目標對象 
  238.         * @param fieldName 目標屬性 
  239.         * @return 目標字段 
  240.         */  
  241.        private static Field getField(Object obj, String fieldName) {  
  242.            Field field = null;  
  243.           for (Class<?> clazz=obj.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {  
  244.               try {  
  245.                   field = clazz.getDeclaredField(fieldName);  
  246.                   break;  
  247.               } catch (NoSuchFieldException e) {  
  248.                   //這裏不用做處理,子類沒有該字段可能對應的父類有,都沒有就返回null。  
  249.               }  
  250.            }  
  251.            return field;  
  252.        }  
  253.    
  254.        /** 
  255.         * 利用反射設置指定對象的指定屬性爲指定的值 
  256.         * @param obj 目標對象 
  257.         * @param fieldName 目標屬性 
  258.          * @param fieldValue 目標值 
  259.         */  
  260.        public static void setFieldValue(Object obj, String fieldName,  
  261.               String fieldValue) {  
  262.            Field field = ReflectUtil.getField(obj, fieldName);  
  263.            if (field != null) {  
  264.               try {  
  265.                   field.setAccessible(true);  
  266.                   field.set(obj, fieldValue);  
  267.               } catch (IllegalArgumentException e) {  
  268.                   // TODO Auto-generated catch block  
  269.                   e.printStackTrace();  
  270.               } catch (IllegalAccessException e) {  
  271.                   // TODO Auto-generated catch block  
  272.                   e.printStackTrace();  
  273.               }  
  274.            }  
  275.         }  
  276.     }  
  277.    
  278. }  

 

       接着我們在Mybatis的配置文件裏面註冊該攔截器:

Xml代碼  收藏代碼
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <!DOCTYPE configuration  
  3.   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
  4.   "http://mybatis.org/dtd/mybatis-3-config.dtd">  
  5. <configuration>  
  6.     <properties resource="config/jdbc.properties"></properties>  
  7.     <typeAliases>  
  8.        <package name="com.tiantian.mybatis.model"/>  
  9.     </typeAliases>  
  10.     <plugins>  
  11.        <plugin interceptor="com.tiantian.mybatis.interceptor.PageInterceptor">  
  12.            <property name="databaseType" value="Oracle"/>  
  13.        </plugin>  
  14.     </plugins>  
  15.     <environments default="development">  
  16.        <environment id="development">  
  17.            <transactionManager type="JDBC" />  
  18.            <dataSource type="POOLED">  
  19.               <property name="driver" value="${jdbc.driver}" />  
  20.               <property name="url" value="${jdbc.url}" />  
  21.                <property name="username" value="${jdbc.username}" />  
  22.               <property name="password" value="${jdbc.password}" />  
  23.            </dataSource>  
  24.        </environment>  
  25.     </environments>  
  26.     <mappers>  
  27.        <mapper resource="com/tiantian/mybatis/mapper/UserMapper.xml"/>  
  28.     </mappers>  

 

       這樣我們的攔截器就已經定義並且配置好了,接下來我們就來測試一下。假設在我們的UserMapper.xml中有如下這樣一個Mapper映射信息:

Xml代碼  收藏代碼
  1. <select id="findPage" resultType="User" parameterType="page">  
  2.    select * from t_user  
  3. </select>  

 

       那我們就可以這樣來測試它:

Java代碼  收藏代碼
  1. SqlSession sqlSession = sqlSessionFactory.openSession();  
  2. try {  
  3.     UserMapper userMapper = sqlSession.getMapper(UserMapper.class);  
  4.     Page<User> page = new Page<User>();  
  5.     page.setPageNo(2);  
  6.     List<User> users = userMapper.findPage(page);  
  7.     page.setResults(users);  
  8.     System.out.println(page);  
  9. finally {  
  10.     sqlSession.close();  
  11. }  

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章