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

mybatis的攔截器實現(xiàn)分頁(動態(tài)代理)

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

代人買票

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

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

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

注冊公司 ------ plugin
申報資產(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");
}

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

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

開始代購

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

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

7.把屬性值為新的sql

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容