MyBatis使用攔截器實(shí)現(xiàn)分頁(yè)功能

mybatis的攔截器實(shí)現(xiàn)分頁(yè)(動(dòng)態(tài)代理)

攔截sql語(yǔ)句來(lái)實(shí)現(xiàn)分頁(yè)
1.攔截什么樣的對(duì)象(以page作為參數(shù)傳入;page對(duì)象)
2.攔截對(duì)象什么行為
3.什么時(shí)候攔截 (在prepareStatement的時(shí)候攔截)

代人買票

mybatis獲取statement其實(shí)是在statementHandler中,這是一個(gè)處理接口,有個(gè)prepare方法,返回Statement,這個(gè)方法是在BaseStatementHandler中實(shí)現(xiàn)的,statement是在instantiateStatement這個(gè)方法中獲取的,這個(gè)方法是一個(gè)抽象方法,看它的PrepareStatementHandler實(shí)現(xiàn),在這里邊看到了connection.prepareStatement(sql,PreparedStatement.),也就是和JDBC類似的代碼了,這就是分頁(yè)攔截器要攔截的位置了。如何實(shí)現(xiàn)攔截呢?mybatis提供了相應(yīng)的注解:

@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})})
//成立代購(gòu)公司 ----  implements Interceptor
public class PageInterceptor implements Interceptor {

①type指向要連接的接口class,這里指向StatementHandler.class, ②Method指向要攔截的方法,這里是prepare
③args[]攔截的方法的參數(shù)類型,這里是Connection.class
這樣就準(zhǔn)確描述了要攔截StatementHandler接口下的prepare方法。目標(biāo)確定,接下來(lái)就可以做手腳了,在PrepareStatementHandler拿到sql語(yǔ)句之前將這個(gè)sql語(yǔ)句改裝成我們的分頁(yè)sql,然后在塞回去,讓程序繼續(xù)執(zhí)行,這樣就成功了。
注意:過(guò)早過(guò)遲的攔截都不合適。所以在PreparedStatement pstmt=conn.prepareStatement(sql.toString());之前攔截即可(把SQL語(yǔ)句處理再放進(jìn)去提交)

注冊(cè)公司 ------ plugin
申報(bào)資產(chǎn) ----- property

<plugins>
  <plugin interceptor="com.imooc.interceptor.PageInterceptor">
    <property name="test" value="abc"/>
  </plugin>
</plugins> 

使用資產(chǎn)

public class PageInterceptor implements Interceptor {
private String test;
// 執(zhí)行順序 1
@Override
public void setProperties(Properties properties) {
    this.test = properties.getProperty("test");
}

識(shí)別哪些是去買票的人
(并不一定就是需要找代購(gòu)的人,在正式代購(gòu)的時(shí)候會(huì)更精確的定位客戶群體)

// 執(zhí)行順序 2
@Override
public Object plugin(Object target) {
    System.out.println(this.test);
    return Plugin.wrap(target, this);
}
 plugin(Object target)方法參數(shù)就是被攔截的對(duì)象target,返回的就是滿足條件的代理類,Plugin.wrap(target,this):this也就是自定義攔截器實(shí)例,通過(guò)獲取注解得到要攔截的類型,比較target的類型與this獲取的要攔截的類型是不是一致,如果滿足條件就獲取代理對(duì)象,并執(zhí)行intercept方法,沒(méi)有獲取代理對(duì)象的將直接返回,不會(huì)經(jīng)過(guò)intercept方法。

開始代購(gòu)

// 執(zhí)行順序 3
@Override
public Object intercept(Invocation invocation) throws Throwable {
 //攔截器的參數(shù)(Invocation)中保存了攔截器所攔截的所有對(duì)象,根據(jù)方法簽名,這里僅僅只是對(duì)statementHandler中的關(guān)鍵信息進(jìn)行處理,原理就是使用分頁(yè)的sql替換攔截到的原始sql,攔截對(duì)象類型是StatementHandler,由方法簽名決定的
    StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
//StatementHandler將對(duì)配置文件中的sql語(yǔ)句進(jìn)行處理(sql語(yǔ)句在MappedStatement中),但是在StatementHandler中,所有的對(duì)象屬性均為受保護(hù)的以及私有的,首先想到的是通過(guò)反射讀寫信息,幸好Mybatis已經(jīng)有一個(gè)類MetaObject,有個(gè)方法 MetaObject.forObject(statementHandler,__,__)可以對(duì)注解的攔截方法簽名所對(duì)應(yīng)的對(duì)象進(jìn)行包裝,這樣我們得到的是被包裝的statementHandler
    MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY);
    MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
    // 配置文件中SQL語(yǔ)句的ID
    String id = mappedStatement.getId();
           //定位更精準(zhǔn)的用戶群體(嫌麻煩不愿排隊(duì)的)
    if(id.matches(".+ByPage$")) {
        BoundSql boundSql = statementHandler.getBoundSql();
通過(guò)BoundSql獲得原始的sql語(yǔ)句之后,再次使用的是BoundSql的getParameterObject()來(lái)獲取配置文件中的參數(shù),因?yàn)榈玫降膮?shù)是一個(gè)map,調(diào)用對(duì)象的get方法得到Page對(duì)象,得到page對(duì)象之后就可以拼接分頁(yè)sql了。metaObject.setValue(“delegate.boundSql.sql”,pageSql)修改原本不可以修改的值,修改原來(lái)的屬性值為新的sql。
mybatis通過(guò)Invocation這個(gè)參數(shù)的proceed()方法交回主權(quán),這個(gè)方法的源碼 return method.invoke(target,args)
        // 原始的SQL語(yǔ)句
        String sql = boundSql.getSql();
        // 查詢總條數(shù)的SQL語(yǔ)句
//這里的問(wèn)題在于sql是否能執(zhí)行以及如何執(zhí)行,需要connection對(duì)象,而此對(duì)象就是方法簽名的參數(shù),可以通過(guò)invocation.getArgs()[0]獲得,然后通過(guò)connection.prepareStatement(countSql)將拼接好的sql語(yǔ)句進(jìn)行預(yù)編譯,并執(zhí)行,就可以獲得結(jié)果,由于此結(jié)果是統(tǒng)計(jì)總數(shù)的,只有一條記錄,將此記錄轉(zhuǎn)換為int類型,并賦值給page對(duì)象。
        String countSql = "select count(*) from (" + sql + ")a";
        Connection connection = (Connection)invocation.getArgs()[0];
        PreparedStatement countStatement = connection.prepareStatement(countSql);
        ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
        parameterHandler.setParameters(countStatement);
        ResultSet rs = countStatement.executeQuery();
        
        Map<?,?> parameter = (Map<?,?>)boundSql.getParameterObject();
        //獲取購(gòu)票信息
        Page page = (Page)parameter.get("page");
        if(rs.next()) {
            page.setTotalNumber(rs.getInt(1));
        }
        // 改造后帶分頁(yè)查詢的SQL語(yǔ)句
        //代購(gòu)公司 開始購(gòu)票
        String pageSql = sql + " limit " + page.getDbIndex() + "," + page.getDbNumber();
               
        metaObject.setValue("delegate.boundSql.sql", pageSql);
    }
    return invocation.proceed();
}
代購(gòu)過(guò)程總結(jié):

1.RoutingStatementHandler
2.通過(guò)RoutingStatementHandler對(duì)象的屬性delegate找到statement實(shí)現(xiàn)類BaseStatementHandler
3.通過(guò)BaseStatementHandler類的反射得到對(duì)象的MappedStatement對(duì)象
4.通過(guò)MappedStatement的屬性getID得到配置文件sql語(yǔ)句的ID
5.通過(guò)BaseStatementHandler屬性的到原始sql語(yǔ)句
6.拼接分頁(yè)sql(
1.需要查詢總數(shù)的sql
2.通過(guò)攔截Connection對(duì)象得到PrepareStatement對(duì)象
3.得到對(duì)應(yīng)的參數(shù)
4.把參數(shù)設(shè)到prepareStatement對(duì)象里的?(該?號(hào)在配置文件以#{}形式存在,mybatis會(huì)把它轉(zhuǎn)為?號(hào))
5.執(zhí)行sql語(yǔ)句
6.得到總數(shù)

7.把屬性值為新的sql

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容