Mybatis?攔截器介紹
1.1 目錄
1.2 前言
1.3 Interceptor接口
1.4 注冊攔截器
1.5 Mybatis可攔截的方法
1.6 利用攔截器進行分頁
?????? 攔截器的一個作用就是我們可以攔截某些方法的調用,我們可以選擇在這些被攔截的方法執行前后加上某些邏輯,也可以在執行這些被攔截的方法時執行自己的邏輯而不再執行被攔截的方法。Mybatis攔截器設計的一個初衷就是為了供用戶在某些時候可以實現自己的邏輯而不必去動Mybatis固有的邏輯。打個比方,對于Executor,Mybatis中有幾種實現:BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor。這個時候如果你覺得這幾種實現對于Executor接口的query方法都不能滿足你的要求,那怎么辦呢?是要去改源碼嗎?當然不。我們可以建立一個Mybatis攔截器用于攔截Executor接口的query方法,在攔截之后實現自己的query方法邏輯,之后可以選擇是否繼續執行原來的query方法。
?????? 對于攔截器Mybatis為我們提供了一個Interceptor接口,通過實現該接口就可以定義我們自己的攔截器。我們先來看一下這個接口的定義:
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?#251throw?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:
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方法進行攔截,而執行這兩個接口對象的其他方法時都只是做一個簡單的代理。
?????? 注冊攔截器是通過在Mybatis配置文件中plugins元素下的plugin元素來進行的。一個plugin對應著一個攔截器,在plugin元素下面我們可以指定若干個property子元素。Mybatis在注冊定義的攔截器時會先把對應攔截器下面的所有property通過Interceptor的setProperties方法注入給對應的攔截器。所以,我們可以這樣來注冊我們在前面定義的MyInterceptor:
<?xml?version="1.0"?encoding="UTF-8"??>
<!DOCTYPE?configurationPUBLIC?"-//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>
Mybatis攔截器只能攔截四種類型的接口:Executor、StatementHandler、ParameterHandler和ResultSetHandler。這是在Mybatis的Configuration中寫死了的,如果要支持攔截其他接口就需要我們重寫Mybatis的Configuration。Mybatis可以對這四個接口中所有的方法進行攔截。
?????? 下面將介紹一個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;//每頁顯示的記錄數,默認是15private?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;}@Overridepublic?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?>=?16return?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?blocke.printStackTrace();}?catch?(IllegalAccessException?e)?{//?TODO?Auto-generated?catch?blocke.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?blocke.printStackTrace();}?catch?(IllegalAccessException?e)?{//?TODO?Auto-generated?catch?blocke.printStackTrace();}}}}}
接著我們在Mybatis的配置文件里面注冊該攔截器:
<?xml?version="1.0"?encoding="UTF-8"??>
<!DOCTYPE?configurationPUBLIC?"-//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>
這樣我們的攔截器就已經定義并且配置好了,接下來我們就來測試一下。假設在我們的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();}
轉載于:https://blog.51cto.com/4925054/2131510