Mybatis 拦截器介绍
Mybatis?攔截器介紹
1.1 目錄
1.2 前言
1.3 Interceptor接口
1.4 注冊(cè)攔截器
1.5 Mybatis可攔截的方法
1.6 利用攔截器進(jìn)行分頁(yè)
?????? 攔截器的一個(gè)作用就是我們可以攔截某些方法的調(diào)用,我們可以選擇在這些被攔截的方法執(zhí)行前后加上某些邏輯,也可以在執(zhí)行這些被攔截的方法時(shí)執(zhí)行自己的邏輯而不再執(zhí)行被攔截的方法。Mybatis攔截器設(shè)計(jì)的一個(gè)初衷就是為了供用戶在某些時(shí)候可以實(shí)現(xiàn)自己的邏輯而不必去動(dòng)Mybatis固有的邏輯。打個(gè)比方,對(duì)于Executor,Mybatis中有幾種實(shí)現(xiàn):BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor。這個(gè)時(shí)候如果你覺得這幾種實(shí)現(xiàn)對(duì)于Executor接口的query方法都不能滿足你的要求,那怎么辦呢?是要去改源碼嗎?當(dāng)然不。我們可以建立一個(gè)Mybatis攔截器用于攔截Executor接口的query方法,在攔截之后實(shí)現(xiàn)自己的query方法邏輯,之后可以選擇是否繼續(xù)執(zhí)行原來的query方法。
?????? 對(duì)于攔截器Mybatis為我們提供了一個(gè)Interceptor接口,通過實(shí)現(xiàn)該接口就可以定義我們自己的攔截器。我們先來看一下這個(gè)接口的定義:
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);}我們可以看到在該接口中一共定義有三個(gè)方法,intercept、plugin和setProperties。plugin方法是攔截器用于封裝目標(biāo)對(duì)象的,通過該方法我們可以返回目標(biāo)對(duì)象本身,也可以返回一個(gè)它的代理。當(dāng)返回的是代理的時(shí)候我們可以對(duì)其中的方法進(jìn)行攔截來調(diào)用intercept方法,當(dāng)然也可以調(diào)用其他方法,這點(diǎn)將在后文講解。setProperties方法是用于在Mybatis配置文件中指定一些屬性的。
?????? 定義自己的Interceptor最重要的是要實(shí)現(xiàn)plugin方法和intercept方法,在plugin方法中我們可以決定是否要進(jìn)行攔截進(jìn)而決定要返回一個(gè)什么樣的目標(biāo)對(duì)象。而intercept方法就是要進(jìn)行攔截的時(shí)候要執(zhí)行的方法。
?????? 對(duì)于plugin方法而言,其實(shí)Mybatis已經(jīng)為我們提供了一個(gè)實(shí)現(xiàn)。Mybatis中有一個(gè)叫做Plugin的類,里面有一個(gè)靜態(tài)方法wrap(Object target,Interceptor interceptor),通過該方法可以決定要返回的對(duì)象是目標(biāo)對(duì)象還是對(duì)應(yīng)的代理。這里我們先來看一下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方法,它根據(jù)當(dāng)前的Interceptor上面的注解定義哪些接口需要攔截,然后判斷當(dāng)前目標(biāo)對(duì)象是否有實(shí)現(xiàn)對(duì)應(yīng)需要攔截的接口,如果沒有則返回目標(biāo)對(duì)象本身,如果有則返回一個(gè)代理對(duì)象。而這個(gè)代理對(duì)象的InvocationHandler正是一個(gè)Plugin。所以當(dāng)目標(biāo)對(duì)象在執(zhí)行接口方法時(shí),如果是通過代理對(duì)象執(zhí)行的,則會(huì)調(diào)用對(duì)應(yīng)InvocationHandler的invoke方法,也就是Plugin的invoke方法。所以接著我們來看一下該invoke方法的內(nèi)容。這里invoke方法的邏輯是:如果當(dāng)前執(zhí)行的方法是定義好的需要攔截的方法,則把目標(biāo)對(duì)象、要執(zhí)行的方法以及方法參數(shù)封裝成一個(gè)Invocation對(duì)象,再把封裝好的Invocation作為參數(shù)傳遞給當(dāng)前攔截器的intercept方法。如果不需要攔截,則直接調(diào)用當(dāng)前的方法。Invocation中定義了定義了一個(gè)proceed方法,其邏輯就是調(diào)用當(dāng)前方法,所以如果在intercept中需要繼續(xù)調(diào)用當(dāng)前方法的話可以調(diào)用invocation的procced方法。
?????? 這就是Mybatis中實(shí)現(xiàn)Interceptor攔截的一個(gè)思想,如果用戶覺得這個(gè)思想有問題或者不能完全滿足你的要求的話可以通過實(shí)現(xiàn)自己的Plugin來決定什么時(shí)候需要代理什么時(shí)候需要攔截。以下講解的內(nèi)容都是基于Mybatis的默認(rèn)實(shí)現(xiàn)即通過Plugin來管理Interceptor來講解的。
?????? 對(duì)于實(shí)現(xiàn)自己的Interceptor而言有兩個(gè)很重要的注解,一個(gè)是@Intercepts,其值是一個(gè)@Signature數(shù)組。@Intercepts用于表明當(dāng)前的對(duì)象是一個(gè)Interceptor,而@Signature則表明要攔截的接口、方法以及對(duì)應(yīng)的參數(shù)類型。來看一個(gè)自定義的簡(jiǎn)單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方法,這個(gè)方法在Configuration初始化當(dāng)前的Interceptor時(shí)就會(huì)執(zhí)行,這里只是簡(jiǎn)單的取兩個(gè)屬性進(jìn)行打印。
?????? 其次看plugin方法中我們是用的Plugin的邏輯來實(shí)現(xiàn)Mybatis的邏輯的。
?????? 接著看MyInterceptor類上我們用@Intercepts標(biāo)記了這是一個(gè)Interceptor,然后在@Intercepts中定義了兩個(gè)@Signature,即兩個(gè)攔截點(diǎn)。第一個(gè)@Signature我們定義了該Interceptor將攔截Executor接口中參數(shù)類型為MappedStatement、Object、RowBounds和ResultHandler的query方法;第二個(gè)@Signature我們定義了該Interceptor將攔截StatementHandler中參數(shù)類型為Connection的prepare方法。
?????? 最后再來看一下intercept方法,這里我們只是簡(jiǎn)單的打印了一句話,然后調(diào)用invocation的proceed方法,使當(dāng)前方法正常的調(diào)用。
?????? 對(duì)于這個(gè)攔截器,Mybatis在注冊(cè)該攔截器的時(shí)候就會(huì)利用定義好的n個(gè)property作為參數(shù)調(diào)用該攔截器的setProperties方法。之后在新建可攔截對(duì)象的時(shí)候會(huì)調(diào)用該攔截器的plugin方法來決定是返回目標(biāo)對(duì)象本身還是代理對(duì)象。對(duì)于這個(gè)攔截器而言,當(dāng)Mybatis是要Executor或StatementHandler對(duì)象的時(shí)候就會(huì)返回一個(gè)代理對(duì)象,其他都是原目標(biāo)對(duì)象本身。然后當(dāng)Executor代理對(duì)象在執(zhí)行參數(shù)類型為MappedStatement、Object、RowBounds和ResultHandler的query方法或StatementHandler代理對(duì)象在執(zhí)行參數(shù)類型為Connection的prepare方法時(shí)就會(huì)觸發(fā)當(dāng)前的攔截器的intercept方法進(jìn)行攔截,而執(zhí)行這兩個(gè)接口對(duì)象的其他方法時(shí)都只是做一個(gè)簡(jiǎn)單的代理。
?????? 注冊(cè)攔截器是通過在Mybatis配置文件中plugins元素下的plugin元素來進(jìn)行的。一個(gè)plugin對(duì)應(yīng)著一個(gè)攔截器,在plugin元素下面我們可以指定若干個(gè)property子元素。Mybatis在注冊(cè)定義的攔截器時(shí)會(huì)先把對(duì)應(yīng)攔截器下面的所有property通過Interceptor的setProperties方法注入給對(duì)應(yīng)的攔截器。所以,我們可以這樣來注冊(cè)我們?cè)谇懊娑x的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中寫死了的,如果要支持?jǐn)r截其他接口就需要我們重寫Mybatis的Configuration。Mybatis可以對(duì)這四個(gè)接口中所有的方法進(jìn)行攔截。
?????? 下面將介紹一個(gè)Mybatis攔截器的實(shí)際應(yīng)用。Mybatis攔截器常常會(huì)被用來進(jìn)行分頁(yè)處理。我們知道要利用JDBC對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作就必須要有一個(gè)對(duì)應(yīng)的Statement對(duì)象,Mybatis在執(zhí)行Sql語(yǔ)句前也會(huì)產(chǎn)生一個(gè)包含Sql語(yǔ)句的Statement對(duì)象,而且對(duì)應(yīng)的Sql語(yǔ)句是在Statement之前產(chǎn)生的,所以我們就可以在它成Statement之前對(duì)用來生成Statement的Sql語(yǔ)句下手。在Mybatis中Statement語(yǔ)句是通過RoutingStatementHandler對(duì)象的prepare方法生成的。所以利用攔截器實(shí)現(xiàn)Mybatis分頁(yè)的一個(gè)思路就是攔截StatementHandler接口的prepare方法,然后在攔截器方法中把Sql語(yǔ)句改成對(duì)應(yīng)的分頁(yè)查詢Sql語(yǔ)句,之后再調(diào)用StatementHandler對(duì)象的prepare方法,即調(diào)用invocation.proceed()。更改Sql語(yǔ)句這個(gè)看起來很簡(jiǎn)單,而事實(shí)上來說的話就沒那么直觀,因?yàn)榘╯ql等其他屬性在內(nèi)的多個(gè)屬性都沒有對(duì)應(yīng)的方法可以直接取到,它們對(duì)外部都是封閉的,是對(duì)象的私有屬性,所以這里就需要引入反射機(jī)制來獲取或者更改對(duì)象的私有屬性的值了。對(duì)于分頁(yè)而言,在攔截器里面我們常常還需要做的一個(gè)操作就是統(tǒng)計(jì)滿足當(dāng)前條件的記錄一共有多少,這是通過獲取到了原始的Sql語(yǔ)句后,把它改為對(duì)應(yīng)的統(tǒng)計(jì)語(yǔ)句再利用Mybatis封裝好的參數(shù)和設(shè)置參數(shù)的功能把Sql語(yǔ)句中的參數(shù)進(jìn)行替換,之后再執(zhí)行查詢記錄數(shù)的Sql語(yǔ)句進(jìn)行總記錄數(shù)的統(tǒng)計(jì)。先來看一個(gè)我們對(duì)分頁(yè)操作封裝的一個(gè)實(shí)體類Page:
import?java.util.HashMap; import?java.util.List; import?java.util.Map;/***?對(duì)分頁(yè)的基本數(shù)據(jù)進(jìn)行一個(gè)簡(jiǎn)單的封裝*/ public?class?Page<T>?{private?int?pageNo?=?1;//頁(yè)碼,默認(rèn)是第一頁(yè)private?int?pageSize?=?15;//每頁(yè)顯示的記錄數(shù),默認(rèn)是15private?int?totalRecord;//總記錄數(shù)private?int?totalPage;//總頁(yè)數(shù)private?List<T>?results;//對(duì)應(yīng)的當(dāng)前頁(yè)記錄private?Map<String,?Object>?params?=?new?HashMap<String,?Object>();//其他的參數(shù)我們把它分裝成一個(gè)Map對(duì)象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;//在設(shè)置總頁(yè)數(shù)的時(shí)候計(jì)算出對(duì)應(yīng)的總頁(yè)數(shù),在下面的三目運(yùn)算中加法擁有更高的優(yōu)先級(jí),所以最后可以不加括號(hào)。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();}}?? 對(duì)于需要進(jìn)行分頁(yè)的Mapper映射,我們會(huì)給它傳一個(gè)Page對(duì)象作為參數(shù),我們可以看到Page對(duì)象里面包括了一些分頁(yè)的基本信息,這些信息我們可以在攔截器里面用到,然后我們把除分頁(yè)的基本信息以外的其他參數(shù)用一個(gè)Map對(duì)象進(jìn)行包裝,這樣在Mapper映射語(yǔ)句中的其他參數(shù)就可以從Map中取值了。接著來看一下我們的PageInterceptor的定義,對(duì)于PageInterceptor我就不做過多的說明,代碼里面附有很詳細(xì)的注釋信息:
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;/****?分頁(yè)攔截器,用于攔截需要進(jìn)行分頁(yè)查詢的操作,然后對(duì)其進(jìn)行分頁(yè)處理。*?利用攔截器實(shí)現(xiàn)Mybatis分頁(yè)的原理:*?要利用JDBC對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作就必須要有一個(gè)對(duì)應(yīng)的Statement對(duì)象,Mybatis在執(zhí)行Sql語(yǔ)句前就會(huì)產(chǎn)生一個(gè)包含Sql語(yǔ)句的Statement對(duì)象,而且對(duì)應(yīng)的Sql語(yǔ)句*?是在Statement之前產(chǎn)生的,所以我們就可以在它生成Statement之前對(duì)用來生成Statement的Sql語(yǔ)句下手。在Mybatis中Statement語(yǔ)句是通過RoutingStatementHandler對(duì)象的*?prepare方法生成的。所以利用攔截器實(shí)現(xiàn)Mybatis分頁(yè)的一個(gè)思路就是攔截StatementHandler接口的prepare方法,然后在攔截器方法中把Sql語(yǔ)句改成對(duì)應(yīng)的分頁(yè)查詢Sql語(yǔ)句,之后再調(diào)用*?StatementHandler對(duì)象的prepare方法,即調(diào)用invocation.proceed()。*?對(duì)于分頁(yè)而言,在攔截器里面我們還需要做的一個(gè)操作就是統(tǒng)計(jì)滿足當(dāng)前條件的記錄一共有多少,這是通過獲取到了原始的Sql語(yǔ)句后,把它改為對(duì)應(yīng)的統(tǒng)計(jì)語(yǔ)句再利用Mybatis封裝好的參數(shù)和設(shè)*?置參數(shù)的功能把Sql語(yǔ)句中的參數(shù)進(jìn)行替換,之后再執(zhí)行查詢記錄數(shù)的Sql語(yǔ)句進(jìn)行總記錄數(shù)的統(tǒng)計(jì)。**/ @Intercepts(?{@Signature(method?=?"prepare",?type?=?StatementHandler.class,?args?=?{Connection.class})?}) public?class?PageInterceptor?implements?Interceptor?{private?String?databaseType;//數(shù)據(jù)庫(kù)類型,不同的數(shù)據(jù)庫(kù)有不同的分頁(yè)方法/***?攔截后要執(zhí)行的方法*/public?Object?intercept(Invocation?invocation)?throws?Throwable?{//對(duì)于StatementHandler其實(shí)只有兩個(gè)實(shí)現(xiàn)類,一個(gè)是RoutingStatementHandler,另一個(gè)是抽象類BaseStatementHandler,//BaseStatementHandler有三個(gè)子類,分別是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler,//SimpleStatementHandler是用于處理Statement的,PreparedStatementHandler是處理PreparedStatement的,而CallableStatementHandler是//處理CallableStatement的。Mybatis在進(jìn)行Sql語(yǔ)句處理的時(shí)候都是建立的RoutingStatementHandler,而在RoutingStatementHandler里面擁有一個(gè)//StatementHandler類型的delegate屬性,RoutingStatementHandler會(huì)依據(jù)Statement的不同建立對(duì)應(yīng)的BaseStatementHandler,即SimpleStatementHandler、//PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler里面所有StatementHandler接口方法的實(shí)現(xiàn)都是調(diào)用的delegate對(duì)應(yīng)的方法。//我們?cè)赑ageInterceptor類上已經(jīng)用@Signature標(biāo)記了該Interceptor只攔截StatementHandler接口的prepare方法,又因?yàn)镸ybatis只有在建立RoutingStatementHandler的時(shí)候//是通過Interceptor的plugin方法進(jìn)行包裹的,所以我們這里攔截到的目標(biāo)對(duì)象肯定是RoutingStatementHandler對(duì)象。RoutingStatementHandler?handler?=?(RoutingStatementHandler)?invocation.getTarget();//通過反射獲取到當(dāng)前RoutingStatementHandler對(duì)象的delegate屬性StatementHandler?delegate?=?(StatementHandler)ReflectUtil.getFieldValue(handler,?"delegate");//獲取到當(dāng)前StatementHandler的?boundSql,這里不管是調(diào)用handler.getBoundSql()還是直接調(diào)用delegate.getBoundSql()結(jié)果是一樣的,因?yàn)橹耙呀?jīng)說過了//RoutingStatementHandler實(shí)現(xiàn)的所有StatementHandler接口方法里面都是調(diào)用的delegate對(duì)應(yīng)的方法。BoundSql?boundSql?=?delegate.getBoundSql();//拿到當(dāng)前綁定Sql的參數(shù)對(duì)象,就是我們?cè)谡{(diào)用對(duì)應(yīng)的Mapper映射語(yǔ)句時(shí)所傳入的參數(shù)對(duì)象Object?obj?=?boundSql.getParameterObject();//這里我們簡(jiǎn)單的通過傳入的是Page對(duì)象就認(rèn)定它是需要進(jìn)行分頁(yè)操作的。if?(obj?instanceof?Page<?>)?{Page<?>?page?=?(Page<?>)?obj;//通過反射獲取delegate父類BaseStatementHandler的mappedStatement屬性MappedStatement?mappedStatement?=?(MappedStatement)ReflectUtil.getFieldValue(delegate,?"mappedStatement");//攔截到的prepare方法參數(shù)是一個(gè)Connection對(duì)象Connection?connection?=?(Connection)invocation.getArgs()[0];//獲取當(dāng)前要執(zhí)行的Sql語(yǔ)句,也就是我們直接在Mapper映射語(yǔ)句中寫的Sql語(yǔ)句String?sql?=?boundSql.getSql();//給當(dāng)前的page參數(shù)對(duì)象設(shè)置總記錄數(shù)this.setTotalRecord(page,mappedStatement,?connection);//獲取分頁(yè)Sql語(yǔ)句String?pageSql?=?this.getPageSql(page,?sql);//利用反射設(shè)置當(dāng)前BoundSql對(duì)應(yīng)的sql屬性為我們建立好的分頁(yè)Sql語(yǔ)句ReflectUtil.setFieldValue(boundSql,?"sql",?pageSql);}return?invocation.proceed();}/***?攔截器對(duì)應(yīng)的封裝原始對(duì)象的方法*/public?Object?plugin(Object?target)?{return?Plugin.wrap(target,?this);}/***?設(shè)置注冊(cè)攔截器時(shí)設(shè)定的屬性*/public?void?setProperties(Properties?properties)?{this.databaseType?=?properties.getProperty("databaseType");}/***?根據(jù)page對(duì)象獲取對(duì)應(yīng)的分頁(yè)查詢Sql語(yǔ)句,這里只做了兩種數(shù)據(jù)庫(kù)類型,Mysql和Oracle*?其它的數(shù)據(jù)庫(kù)都?沒有進(jìn)行分頁(yè)**?@param?page?分頁(yè)對(duì)象*?@param?sql?原sql語(yǔ)句*?@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數(shù)據(jù)庫(kù)的分頁(yè)查詢語(yǔ)句*?@param?page?分頁(yè)對(duì)象*?@param?sqlBuffer?包含原sql語(yǔ)句的StringBuffer對(duì)象*?@return?Mysql數(shù)據(jù)庫(kù)分頁(yè)語(yǔ)句*/private?String?getMysqlPageSql(Page<?>?page,?StringBuffer?sqlBuffer)?{//計(jì)算第一條記錄的位置,Mysql中記錄的位置是從0開始的。int?offset?=?(page.getPageNo()?-?1)?*?page.getPageSize();sqlBuffer.append("?limit?").append(offset).append(",").append(page.getPageSize());return?sqlBuffer.toString();}/***?獲取Oracle數(shù)據(jù)庫(kù)的分頁(yè)查詢語(yǔ)句*?@param?page?分頁(yè)對(duì)象*?@param?sqlBuffer?包含原sql語(yǔ)句的StringBuffer對(duì)象*?@return?Oracle數(shù)據(jù)庫(kù)的分頁(yè)查詢語(yǔ)句*/private?String?getOraclePageSql(Page<?>?page,?StringBuffer?sqlBuffer)?{//計(jì)算第一條記錄的位置,Oracle分頁(yè)是通過rownum進(jìn)行的,而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語(yǔ)句拼接之后大概是這個(gè)樣子://select?*?from?(select?u.*,?rownum?r?from?(select?*?from?t_user)?u?where?rownum?<?31)?where?r?>=?16return?sqlBuffer.toString();}/***?給當(dāng)前的參數(shù)對(duì)象page設(shè)置總記錄數(shù)**?@param?page?Mapper映射語(yǔ)句對(duì)應(yīng)的參數(shù)對(duì)象*?@param?mappedStatement?Mapper映射語(yǔ)句*?@param?connection?當(dāng)前的數(shù)據(jù)庫(kù)連接*/private?void?setTotalRecord(Page<?>?page,MappedStatement?mappedStatement,?Connection?connection)?{//獲取對(duì)應(yīng)的BoundSql,這個(gè)BoundSql其實(shí)跟我們利用StatementHandler獲取到的BoundSql是同一個(gè)對(duì)象。//delegate里面的boundSql也是通過mappedStatement.getBoundSql(paramObj)方法獲取到的。BoundSql?boundSql?=?mappedStatement.getBoundSql(page);//獲取到我們自己寫在Mapper映射語(yǔ)句中對(duì)應(yīng)的Sql語(yǔ)句String?sql?=?boundSql.getSql();//通過查詢Sql語(yǔ)句獲取到對(duì)應(yīng)的計(jì)算總記錄數(shù)的sql語(yǔ)句String?countSql?=?this.getCountSql(sql);//通過BoundSql獲取對(duì)應(yīng)的參數(shù)映射List<ParameterMapping>?parameterMappings?=?boundSql.getParameterMappings();//利用Configuration、查詢記錄數(shù)的Sql語(yǔ)句countSql、參數(shù)映射關(guān)系parameterMappings和參數(shù)對(duì)象page建立查詢記錄數(shù)對(duì)應(yīng)的BoundSql對(duì)象。BoundSql?countBoundSql?=?new?BoundSql(mappedStatement.getConfiguration(),?countSql,?parameterMappings,?page);//通過mappedStatement、參數(shù)對(duì)象page和BoundSql對(duì)象countBoundSql建立一個(gè)用于設(shè)定參數(shù)的ParameterHandler對(duì)象ParameterHandler?parameterHandler?=?new?DefaultParameterHandler(mappedStatement,?page,?countBoundSql);//通過connection建立一個(gè)countSql對(duì)應(yīng)的PreparedStatement對(duì)象。PreparedStatement?pstmt?=?null;ResultSet?rs?=?null;try?{pstmt?=?connection.prepareStatement(countSql);//通過parameterHandler給PreparedStatement對(duì)象設(shè)置參數(shù)parameterHandler.setParameters(pstmt);//之后就是執(zhí)行獲取總記錄數(shù)的Sql語(yǔ)句和獲取結(jié)果了。rs?=?pstmt.executeQuery();if?(rs.next())?{int?totalRecord?=?rs.getInt(1);//給當(dāng)前的參數(shù)page對(duì)象設(shè)置總記錄數(shù)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();}}}/***?根據(jù)原Sql語(yǔ)句獲取對(duì)應(yīng)的查詢總記錄數(shù)的Sql語(yǔ)句*?@param?sql*?@return*/private?String?getCountSql(String?sql)?{int?index?=?sql.indexOf("from");return?"select?count(*)?"?+?sql.substring(index);}/***?利用反射進(jìn)行操作的一個(gè)工具類**/private?static?class?ReflectUtil?{/***?利用反射獲取指定對(duì)象的指定屬性*?@param?obj?目標(biāo)對(duì)象*?@param?fieldName?目標(biāo)屬性*?@return?目標(biāo)屬性的值*/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;}/***?利用反射獲取指定對(duì)象里面的指定屬性*?@param?obj?目標(biāo)對(duì)象*?@param?fieldName?目標(biāo)屬性*?@return?目標(biāo)字段*/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)?{//這里不用做處理,子類沒有該字段可能對(duì)應(yīng)的父類有,都沒有就返回null。}}return?field;}/***?利用反射設(shè)置指定對(duì)象的指定屬性為指定的值*?@param?obj?目標(biāo)對(duì)象*?@param?fieldName?目標(biāo)屬性*?@param?fieldValue?目標(biāo)值*/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();}}}}}接著我們?cè)贛ybatis的配置文件里面注冊(cè)該攔截器:
<?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>這樣我們的攔截器就已經(jīng)定義并且配置好了,接下來我們就來測(cè)試一下。假設(shè)在我們的UserMapper.xml中有如下這樣一個(gè)Mapper映射信息:
<select?id="findPage"?resultType="User"?parameterType="page">select?*?from?t_user</select>那我們就可以這樣來測(cè)試它:
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();}轉(zhuǎn)載于:https://blog.51cto.com/4925054/2131510
總結(jié)
以上是生活随笔為你收集整理的Mybatis 拦截器介绍的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python中变量和函数的区别_关于py
- 下一篇: redis 操作