Mybatis攔截器介紹及分頁插件

        攔截器的一個做用就是咱們能夠攔截某些方法的調用,咱們能夠選擇在這些被攔截的方法執行先後加上某些邏輯,也能夠在執行這些被攔截的方法時執行本身的邏輯而再也不執行被攔截的方法。Mybatis攔截器設計的一個初衷就是爲了供用戶在某些時候能夠實現本身的邏輯而沒必要去動Mybatis固有的邏輯。打個比方,對於Executor,Mybatis中有幾種實現:BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor。這個時候若是你以爲這幾種實現對於Executor接口的query方法都不能知足你的要求,那怎麼辦呢?是要去改源碼嗎?固然不。咱們能夠創建一個Mybatis攔截器用於攔截Executor接口的query方法,在攔截以後實現本身的query方法邏輯,以後能夠選擇是否繼續執行原來的query方法。java

1.Interceptor接口mysql

對於攔截器Mybatis爲咱們提供了一個Interceptor接口,經過實現該接口就能夠定義咱們本身的攔截器。咱們先來看一下這個接口的定義:sql

package org.apache.ibatis.plugin;  
   
import java.util.Properties;  
   
public interface Interceptor {  
   
  Object intercept(Invocation invocation) throws Throwable;  
   
  Object plugin(Object target);  
   
  void setProperties(Properties properties);  
   
}

 

        咱們能夠看到在該接口中一共定義有三個方法,intercept、plugin和setProperties。plugin方法是攔截器用於封裝目標對象的,經過該方法咱們能夠返回目標對象自己,也能夠返回一個它的代理。當返回的是代理的時候咱們能夠對其中的方法進行攔截來調用intercept方法,固然也能夠調用其餘方法,這點將在後文講解。setProperties方法是用於在Mybatis配置文件中指定一些屬性的。
       定義本身的Interceptor最重要的是要實現plugin方法和intercept方法,在plugin方法中咱們能夠決定是否要進行攔截進而決定要返回一個什麼樣的目標對象。而intercept方法就是要進行攔截的時候要執行的方法。
       對於plugin方法而言,其實Mybatis已經爲咱們提供了一個實現。Mybatis中有一個叫作Plugin的類,裏面有一個靜態方法wrap(Object target,Interceptor interceptor),經過該方法能夠決定要返回的對象是目標對象仍是對應的代理。這裏咱們先來看一下Plugin的源碼:數據庫

package org.apache.ibatis.plugin;  
   
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
import java.util.HashMap;  
import java.util.HashSet;  
import java.util.Map;  
import java.util.Set;  
   
import org.apache.ibatis.reflection.ExceptionUtil;  
   
public class Plugin implements InvocationHandler {  
   
  private Object target;  
  private Interceptor interceptor;  
  private Map<Class<?>, Set<Method>> signatureMap;  
   
  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {  
    this.target = target;  
    this.interceptor = interceptor;  
    this.signatureMap = signatureMap;  
  }  
   
  public static Object wrap(Object target, Interceptor interceptor) {  
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);  
    Class<?> type = target.getClass();  
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);  
    if (interfaces.length > 0) {  
      return Proxy.newProxyInstance(  
          type.getClassLoader(),  
          interfaces,  
          new Plugin(target, interceptor, signatureMap));  
    }  
    return target;  
  }  
   
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
    try {  
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());  
      if (methods != null && methods.contains(method)) {  
        return interceptor.intercept(new Invocation(target, method, args));  
      }  
      return method.invoke(target, args);  
    } catch (Exception e) {  
      throw ExceptionUtil.unwrapThrowable(e);  
    }  
  }  
   
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {  
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);  
    if (interceptsAnnotation == null) { // issue #251  
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());       
    }  
    Signature[] sigs = interceptsAnnotation.value();  
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();  
    for (Signature sig : sigs) {  
      Set<Method> methods = signatureMap.get(sig.type());  
      if (methods == null) {  
        methods = new HashSet<Method>();  
        signatureMap.put(sig.type(), methods);  
      }  
      try {  
        Method method = sig.type().getMethod(sig.method(), sig.args());  
        methods.add(method);  
      } catch (NoSuchMethodException e) {  
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);  
      }  
    }  
    return signatureMap;  
  }  
   
  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {  
    Set<Class<?>> interfaces = new HashSet<Class<?>>();  
    while (type != null) {  
      for (Class<?> c : type.getInterfaces()) {  
        if (signatureMap.containsKey(c)) {  
          interfaces.add(c);  
        }  
      }  
      type = type.getSuperclass();  
    }  
    return interfaces.toArray(new Class<?>[interfaces.size()]);  
  }  
   
}

       咱們先看一下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:apache

package com.tiantian.mybatis.interceptor;  
   
import java.sql.Connection;  
import java.util.Properties;  
   
import org.apache.ibatis.executor.Executor;  
import org.apache.ibatis.executor.statement.StatementHandler;  
import org.apache.ibatis.mapping.MappedStatement;  
import org.apache.ibatis.plugin.Interceptor;  
import org.apache.ibatis.plugin.Intercepts;  
import org.apache.ibatis.plugin.Invocation;  
import org.apache.ibatis.plugin.Plugin;  
import org.apache.ibatis.plugin.Signature;  
import org.apache.ibatis.session.ResultHandler;  
import org.apache.ibatis.session.RowBounds;  
   
@Intercepts( {  
       @Signature(method = "query", type = Executor.class, args = {  
              MappedStatement.class, Object.class, RowBounds.class,  
              ResultHandler.class }),  
       @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })  
public class MyInterceptor implements Interceptor {  
   
    public Object intercept(Invocation invocation) throws Throwable {  
       Object result = invocation.proceed();  
       System.out.println("Invocation.proceed()");  
       return result;  
    }  
   
    public Object plugin(Object target) {  
       return Plugin.wrap(target, this);  
    }  
   
    public void setProperties(Properties properties) {  
       String prop1 = properties.getProperty("prop1");  
       String prop2 = properties.getProperty("prop2");  
       System.out.println(prop1 + "------" + prop2);  
    }  
   
}

       首先看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方法進行攔截,而執行這兩個接口對象的其餘方法時都只是作一個簡單的代理。數組

2.註冊攔截器session

       註冊攔截器是經過在Mybatis配置文件中plugins元素下的plugin元素來進行的。一個plugin對應着一個攔截器,在plugin元素下面咱們能夠指定若干個property子元素。Mybatis在註冊定義的攔截器時會先把對應攔截器下面的全部property經過Interceptor的setProperties方法注入給對應的攔截器。因此,咱們能夠這樣來註冊咱們在前面定義的MyInterceptor:mybatis

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE configuration  
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
  "http://mybatis.org/dtd/mybatis-3-config.dtd">  
<configuration>  
    <properties resource="config/jdbc.properties"></properties>  
    <typeAliases>  
       <package name="com.tiantian.mybatis.model"/>  
    </typeAliases>  
    <plugins>  
       <plugin interceptor="com.tiantian.mybatis.interceptor.MyInterceptor">  
           <property name="prop1" value="prop1"/>  
           <property name="prop2" value="prop2"/>  
       </plugin>  
    </plugins>  
    <environments default="development">  
       <environment id="development">  
           <transactionManager type="JDBC" />  
           <dataSource type="POOLED">  
              <property name="driver" value="${jdbc.driver}" />  
              <property name="url" value="${jdbc.url}" />  
              <property name="username" value="${jdbc.username}" />  
              <property name="password" value="${jdbc.password}" />  
           </dataSource>  
       </environment>  
    </environments>  
    <mappers>  
       <mapper resource="com/tiantian/mybatis/mapper/UserMapper.xml"/>  
    </mappers>  
</configuration>

3.Mybatis可攔截的方法oracle

    Mybatis攔截器只能攔截四種類型的接口:Executor、StatementHandler、ParameterHandler和ResultSetHandler。這是在Mybatis的Configuration中寫死了的,若是要支持攔截其餘接口就須要咱們重寫Mybatis的Configuration。Mybatis能夠對這四個接口中全部的方法進行攔截。app

4.利用攔截器進行分頁

下面將介紹一個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:

import java.util.HashMap;  
import java.util.List;  
import java.util.Map;  
   
/** 
 * 對分頁的基本數據進行一個簡單的封裝 
 */  
public class Page<T> {  
   
    private int pageNo = 1;//頁碼,默認是第一頁  
    private int pageSize = 15;//每頁顯示的記錄數,默認是15  
    private int totalRecord;//總記錄數  
    private int totalPage;//總頁數  
    private List<T> results;//對應的當前頁記錄  
    private Map<String, Object> params = new HashMap<String, Object>();//其餘的參數咱們把它分裝成一個Map對象  
   
    public int getPageNo() {  
       return pageNo;  
    }  
   
    public void setPageNo(int pageNo) {  
       this.pageNo = pageNo;  
    }  
   
    public int getPageSize() {  
       return pageSize;  
    }  
   
    public void setPageSize(int pageSize) {  
       this.pageSize = pageSize;  
    }  
   
    public int getTotalRecord() {  
       return totalRecord;  
    }  
   
    public void setTotalRecord(int totalRecord) {  
       this.totalRecord = totalRecord;  
       //在設置總頁數的時候計算出對應的總頁數,在下面的三目運算中加法擁有更高的優先級,因此最後能夠不加括號。  
       int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1;  
       this.setTotalPage(totalPage);  
    }  
   
    public int getTotalPage() {  
       return totalPage;  
    }  
   
    public void setTotalPage(int totalPage) {  
       this.totalPage = totalPage;  
    }  
   
    public List<T> getResults() {  
       return results;  
    }  
   
    public void setResults(List<T> results) {  
       this.results = results;  
    }  
     
    public Map<String, Object> getParams() {  
       return params;  
    }  
     
    public void setParams(Map<String, Object> params) {  
       this.params = params;  
    }  
   
    @Override  
    public String toString() {  
       StringBuilder builder = new StringBuilder();  
       builder.append("Page [pageNo=").append(pageNo).append(", pageSize=")  
              .append(pageSize).append(", results=").append(results).append(  
                     ", totalPage=").append(totalPage).append(  
                     ", totalRecord=").append(totalRecord).append("]");  
       return builder.toString();  
    }  
   
}

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

package com.tiantian.mybatis.interceptor;  
   
import java.lang.reflect.Field;  
import java.sql.Connection;  
import java.sql.PreparedStatement;  
import java.sql.ResultSet;  
import java.sql.SQLException;  
import java.util.List;  
import java.util.Properties;  
   
import org.apache.ibatis.executor.parameter.ParameterHandler;  
import org.apache.ibatis.executor.statement.RoutingStatementHandler;  
import org.apache.ibatis.executor.statement.StatementHandler;  
import org.apache.ibatis.mapping.BoundSql;  
import org.apache.ibatis.mapping.MappedStatement;  
import org.apache.ibatis.mapping.ParameterMapping;  
import org.apache.ibatis.plugin.Interceptor;  
import org.apache.ibatis.plugin.Intercepts;  
import org.apache.ibatis.plugin.Invocation;  
import org.apache.ibatis.plugin.Plugin;  
import org.apache.ibatis.plugin.Signature;  
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;  
   
import com.tiantian.mybatis.model.Page;  
   
/** 
 * 
 * 分頁攔截器,用於攔截須要進行分頁查詢的操做,而後對其進行分頁處理。 
 * 利用攔截器實現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語句後,把它改成對應的統計語句再利用Mybatis封裝好的參數和設 
 * 置參數的功能把Sql語句中的參數進行替換,以後再執行查詢記錄數的Sql語句進行總記錄數的統計。 
 * 
 */  
@Intercepts( {  
       @Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class}) })  
public class PageInterceptor implements Interceptor {  
   
    private String databaseType;//數據庫類型,不一樣的數據庫有不一樣的分頁方法  
     
    /** 
     * 攔截後要執行的方法 
     */  
    public Object intercept(Invocation invocation) throws Throwable {  
       //對於StatementHandler其實只有兩個實現類,一個是RoutingStatementHandler,另外一個是抽象類BaseStatementHandler,  
       //BaseStatementHandler有三個子類,分別是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler,  
       //SimpleStatementHandler是用於處理Statement的,PreparedStatementHandler是處理PreparedStatement的,而CallableStatementHandler是  
       //處理CallableStatement的。Mybatis在進行Sql語句處理的時候都是創建的RoutingStatementHandler,而在RoutingStatementHandler裏面擁有一個  
       //StatementHandler類型的delegate屬性,RoutingStatementHandler會依據Statement的不一樣創建對應的BaseStatementHandler,即SimpleStatementHandler、  
       //PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler裏面全部StatementHandler接口方法的實現都是調用的delegate對應的方法。  
       //咱們在PageInterceptor類上已經用@Signature標記了該Interceptor只攔截StatementHandler接口的prepare方法,又由於Mybatis只有在創建RoutingStatementHandler的時候  
       //是經過Interceptor的plugin方法進行包裹的,因此咱們這裏攔截到的目標對象確定是RoutingStatementHandler對象。  
       RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();  
       //經過反射獲取到當前RoutingStatementHandler對象的delegate屬性  
       StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");  
       //獲取到當前StatementHandler的 boundSql,這裏不論是調用handler.getBoundSql()仍是直接調用delegate.getBoundSql()結果是同樣的,由於以前已經說過了  
       //RoutingStatementHandler實現的全部StatementHandler接口方法裏面都是調用的delegate對應的方法。  
       BoundSql boundSql = delegate.getBoundSql();  
       //拿到當前綁定Sql的參數對象,就是咱們在調用對應的Mapper映射語句時所傳入的參數對象  
       Object obj = boundSql.getParameterObject();  
       //這裏咱們簡單的經過傳入的是Page對象就認定它是須要進行分頁操做的。  
       if (obj instanceof Page<?>) {  
           Page<?> page = (Page<?>) obj;  
           //經過反射獲取delegate父類BaseStatementHandler的mappedStatement屬性  
           MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement");  
           //攔截到的prepare方法參數是一個Connection對象  
           Connection connection = (Connection)invocation.getArgs()[0];  
           //獲取當前要執行的Sql語句,也就是咱們直接在Mapper映射語句中寫的Sql語句  
           String sql = boundSql.getSql();  
           //給當前的page參數對象設置總記錄數  
           this.setTotalRecord(page,  
                  mappedStatement, connection);  
           //獲取分頁Sql語句  
           String pageSql = this.getPageSql(page, sql);  
           //利用反射設置當前BoundSql對應的sql屬性爲咱們創建好的分頁Sql語句  
           ReflectUtil.setFieldValue(boundSql, "sql", pageSql);  
       }  
       return invocation.proceed();  
    }  
   
    /** 
     * 攔截器對應的封裝原始對象的方法 
     */  
    public Object plugin(Object target) {  
       return Plugin.wrap(target, this);  
    }  
   
    /** 
     * 設置註冊攔截器時設定的屬性 
     */  
    public void setProperties(Properties properties) {  
       this.databaseType = properties.getProperty("databaseType");  
    }  
     
    /** 
     * 根據page對象獲取對應的分頁查詢Sql語句,這裏只作了兩種數據庫類型,Mysql和Oracle 
     * 其它的數據庫都 沒有進行分頁 
     * 
     * @param page 分頁對象 
     * @param sql 原sql語句 
     * @return 
     */  
    private String getPageSql(Page<?> page, String sql) {  
       StringBuffer sqlBuffer = new StringBuffer(sql);  
       if ("mysql".equalsIgnoreCase(databaseType)) {  
           return getMysqlPageSql(page, sqlBuffer);  
       } else if ("oracle".equalsIgnoreCase(databaseType)) {  
           return getOraclePageSql(page, sqlBuffer);  
       }  
       return sqlBuffer.toString();  
    }  
     
    /** 
     * 獲取Mysql數據庫的分頁查詢語句 
     * @param page 分頁對象 
     * @param sqlBuffer 包含原sql語句的StringBuffer對象 
     * @return Mysql數據庫分頁語句 
     */  
    private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) {  
       //計算第一條記錄的位置,Mysql中記錄的位置是從0開始的。  
       int offset = (page.getPageNo() - 1) * page.getPageSize();  
       sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());  
       return sqlBuffer.toString();  
    }  
     
    /** 
     * 獲取Oracle數據庫的分頁查詢語句 
     * @param page 分頁對象 
     * @param sqlBuffer 包含原sql語句的StringBuffer對象 
     * @return Oracle數據庫的分頁查詢語句 
     */  
    private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) {  
       //計算第一條記錄的位置,Oracle分頁是經過rownum進行的,而rownum是從1開始的  
       int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;  
       sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getPageSize());  
       sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);  
       //上面的Sql語句拼接以後大概是這個樣子:  
       //select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16  
       return sqlBuffer.toString();  
    }  
     
    /** 
     * 給當前的參數對象page設置總記錄數 
     * 
     * @param page Mapper映射語句對應的參數對象 
     * @param mappedStatement Mapper映射語句 
     * @param connection 當前的數據庫鏈接 
     */  
    private void setTotalRecord(Page<?> page,  
           MappedStatement mappedStatement, Connection connection) {  
       //獲取對應的BoundSql,這個BoundSql其實跟咱們利用StatementHandler獲取到的BoundSql是同一個對象。  
       //delegate裏面的boundSql也是經過mappedStatement.getBoundSql(paramObj)方法獲取到的。  
       BoundSql boundSql = mappedStatement.getBoundSql(page);  
       //獲取到咱們本身寫在Mapper映射語句中對應的Sql語句  
       String sql = boundSql.getSql();  
       //經過查詢Sql語句獲取到對應的計算總記錄數的sql語句  
       String countSql = this.getCountSql(sql);  
       //經過BoundSql獲取對應的參數映射  
       List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  
       //利用Configuration、查詢記錄數的Sql語句countSql、參數映射關係parameterMappings和參數對象page創建查詢記錄數對應的BoundSql對象。  
       BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);  
       //經過mappedStatement、參數對象page和BoundSql對象countBoundSql創建一個用於設定參數的ParameterHandler對象  
       ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);  
       //經過connection創建一個countSql對應的PreparedStatement對象。  
       PreparedStatement pstmt = null;  
       ResultSet rs = null;  
       try {  
           pstmt = connection.prepareStatement(countSql);  
           //經過parameterHandler給PreparedStatement對象設置參數  
           parameterHandler.setParameters(pstmt);  
           //以後就是執行獲取總記錄數的Sql語句和獲取結果了。  
           rs = pstmt.executeQuery();  
           if (rs.next()) {  
              int totalRecord = rs.getInt(1);  
              //給當前的參數page對象設置總記錄數  
              page.setTotalRecord(totalRecord);  
           }  
       } catch (SQLException e) {  
           e.printStackTrace();  
       } finally {  
           try {  
              if (rs != null)  
                  rs.close();  
               if (pstmt != null)  
                  pstmt.close();  
           } catch (SQLException e) {  
              e.printStackTrace();  
           }  
       }  
    }  
     
    /** 
     * 根據原Sql語句獲取對應的查詢總記錄數的Sql語句 
     * @param sql 
     * @return 
     */  
    private String getCountSql(String sql) {  
       int index = sql.indexOf("from");  
       return "select count(*) " + sql.substring(index);  
    }  
     
    /** 
     * 利用反射進行操做的一個工具類 
     * 
     */  
    private static class ReflectUtil {  
       /** 
        * 利用反射獲取指定對象的指定屬性 
        * @param obj 目標對象 
        * @param fieldName 目標屬性 
        * @return 目標屬性的值 
        */  
       public static Object getFieldValue(Object obj, String fieldName) {  
           Object result = null;  
           Field field = ReflectUtil.getField(obj, fieldName);  
           if (field != null) {  
              field.setAccessible(true);  
              try {  
                  result = field.get(obj);  
              } catch (IllegalArgumentException e) {  
                  // TODO Auto-generated catch block  
                  e.printStackTrace();  
              } catch (IllegalAccessException e) {  
                  // TODO Auto-generated catch block  
                  e.printStackTrace();  
              }  
           }  
           return result;  
       }  
        
       /** 
        * 利用反射獲取指定對象裏面的指定屬性 
        * @param obj 目標對象 
        * @param fieldName 目標屬性 
        * @return 目標字段 
        */  
       private static Field getField(Object obj, String fieldName) {  
           Field field = null;  
          for (Class<?> clazz=obj.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {  
              try {  
                  field = clazz.getDeclaredField(fieldName);  
                  break;  
              } catch (NoSuchFieldException e) {  
                  //這裏不用作處理,子類沒有該字段可能對應的父類有,都沒有就返回null。  
              }  
           }  
           return field;  
       }  
   
       /** 
        * 利用反射設置指定對象的指定屬性爲指定的值 
        * @param obj 目標對象 
        * @param fieldName 目標屬性 
         * @param fieldValue 目標值 
        */  
       public static void setFieldValue(Object obj, String fieldName,  
              String fieldValue) {  
           Field field = ReflectUtil.getField(obj, fieldName);  
           if (field != null) {  
              try {  
                  field.setAccessible(true);  
                  field.set(obj, fieldValue);  
              } catch (IllegalArgumentException e) {  
                  // TODO Auto-generated catch block  
                  e.printStackTrace();  
              } catch (IllegalAccessException e) {  
                  // TODO Auto-generated catch block  
                  e.printStackTrace();  
              }  
           }  
        }  
    }  
   
}

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

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE configuration  
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
  "http://mybatis.org/dtd/mybatis-3-config.dtd">  
<configuration>  
    <properties resource="config/jdbc.properties"></properties>  
    <typeAliases>  
       <package name="com.tiantian.mybatis.model"/>  
    </typeAliases>  
    <plugins>  
       <plugin interceptor="com.tiantian.mybatis.interceptor.PageInterceptor">  
           <property name="databaseType" value="Oracle"/>  
       </plugin>  
    </plugins>  
    <environments default="development">  
       <environment id="development">  
           <transactionManager type="JDBC" />  
           <dataSource type="POOLED">  
              <property name="driver" value="${jdbc.driver}" />  
              <property name="url" value="${jdbc.url}" />  
               <property name="username" value="${jdbc.username}" />  
              <property name="password" value="${jdbc.password}" />  
           </dataSource>  
       </environment>  
    </environments>  
    <mappers>  
       <mapper resource="com/tiantian/mybatis/mapper/UserMapper.xml"/>  
    </mappers>  
 </configuration>

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

<select id="findPage" resultType="User" parameterType="page">  
   select * from t_user  
</select>

那咱們就能夠這樣來測試它:

SqlSession sqlSession = sqlSessionFactory.openSession();  
try {  
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);  
    Page<User> page = new Page<User>();  
    page.setPageNo(2);  
    List<User> users = userMapper.findPage(page);  
    page.setResults(users);  
    System.out.println(page);  
} finally {  
    sqlSession.close();  
}
相關文章
相關標籤/搜索