Mybatis攔截器與自定義分頁(yè)插件的實(shí)現(xiàn)

  • 首先Mybatis 默認(rèn)是內(nèi)存分頁(yè),就是將所有數(shù)據(jù)從磁盤(pán)讀入到內(nèi)存中后,再去分頁(yè)。這樣會(huì)大幅度降低應(yīng)用性能,至于為什么,自己看代碼。

  • Mybatis 的物理分頁(yè)可以Mybatis的攔截器來(lái)做

    • 攔截StatementHandler的prepare 方法,然后在攔截器中把Sql語(yǔ)句更換成對(duì)應(yīng)的分頁(yè)查找查詢(xún)語(yǔ)句,然后再調(diào)用StatementHandler對(duì)象的prepare方法,即調(diào)用invocation.proceed()方法
  • Mybatis攔截器只能攔截四種類(lèi)型的接口:Executor、StatementHandler、ParameterHandler和ResultSetHandler。

  • 一個(gè)簡(jiǎn)單的攔截器例子

  • @Intercept 注解中的@Signature中標(biāo)示的屬性,標(biāo)示當(dāng)前攔截器要攔截的那個(gè)類(lèi)的那個(gè)方法,攔截方法的傳入的參數(shù)

    //攔截StatementHandler.class的prepare 方法!,這個(gè)方法的參數(shù)是一個(gè)Connecttion對(duì)象和Interger對(duì)象
    @Intercepts( {
        @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class ,Integer.class}) })
    public class TestInterceptor implements Interceptor {
        Object intercept(Invocation invocation) throws Throwable{....};

        Object plugin(Object target){....};

        void setProperties(Properties properties){....};
    }
---------------------注意在Mybatis 的XML中注冊(cè)攔截器!--------------------------------
        

現(xiàn)在已經(jīng)掌握一個(gè)攔截器的寫(xiě)法,現(xiàn)在的問(wèn)題就在于,從什么地方著手去攔截?

  • 首先要明白,Mybatis 是對(duì)JDBC的一個(gè)高層次的封裝。而JDBC 在完成數(shù)據(jù)操作的時(shí)候必須要有一個(gè)Statement對(duì)象。而Statement對(duì)應(yīng)的SQL語(yǔ)句是在是在Statement之前產(chǎn)生的。所以我們的思路就是在生成Statement之前對(duì)sql進(jìn)行下手。更改sql語(yǔ)句成我們需要的!
  • 對(duì)于Mybatis,其Statement生成是在RouteStatementHandler中。所以我們要做的就是攔截這個(gè)handler的prepare方法!然后修改Sql語(yǔ)句?。。?!
    //攔截到Sql 并輸出到控制臺(tái)
   @Override
    public Object intercept(Invocation invocation) throws Throwable {
         // 其實(shí)就是代理模式!
        RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
        StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");
        System.out.println(delegate.getBoundSql().getSql());
        //Sy).getClass().getName());stem.out.println(invocation.getTarget(

        return invocation.proceed();
    }

我們知道利用Mybatis查詢(xún)一個(gè)集合時(shí)傳入Rowbounds對(duì)象即可指定其Offset和Limit,只不過(guò)其沒(méi)有利用原生sql去查詢(xún)罷了,我們現(xiàn)在做的,就是通過(guò)攔截器 拿到這個(gè)參數(shù),然后織入到SQL語(yǔ)句中,這樣我們就可以完成一個(gè)物理分頁(yè)!


    package person.walker.interceptor;
    
    import org.apache.ibatis.executor.Executor;
    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.plugin.*;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import sun.reflect.Reflection;
    
    import java.lang.reflect.Field;
    import java.sql.Connection;
    import java.util.Properties;
    
    /**
     * Created by Administrator on 2017/3/1 0001.
     */
    
    @Intercepts({
            @Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class, Integer.class})})
    public class TestInterceptor implements Interceptor {
        // select語(yǔ)句正則表達(dá)式匹配:
        private final static String REGEX = "^\\s*[Ss][Ee][Ll][Ee][Cc][Tt].*$";
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
            // BoundSql類(lèi)中有一個(gè)sql屬性,即為待執(zhí)行的sql語(yǔ)句
            BoundSql boundSql = handler.getBoundSql();
            String sql = boundSql.getSql();
            if (sql.matches(REGEX)) {
                // delegate是RoutingStatementHandler通過(guò)mapper映射文件中設(shè)置的statementType來(lái)指定具體的StatementHandler
                Object delegate = getFieldValue(handler, "delegate");
                // rowBounds,即為Mybais 原生的Sql 分頁(yè)參數(shù),由于Rowbounds 在BaseStateHandler中所以我們需要去找父類(lèi)
                RowBounds rowBounds = (RowBounds) getFieldValue(delegate, "rowBounds");
                // 如果rowBound不為空,且rowBounds的起始位置不為0,則代表我們需要進(jìn)行分頁(yè)處理
                if (rowBounds != null) {
                    // assemSql(...)完成對(duì)sql語(yǔ)句的裝配及rowBounds的重置操作
                    setFieldValue(boundSql, "sql", assemSql(sql, rowBounds));
                }
            }
            return invocation.proceed();
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    
        @Override
        public void setProperties(Properties properties) {
            String prop1 = properties.getProperty("prop1");
            String prop2 = properties.getProperty("prop2");
            System.out.println(prop1 + "------" + prop2);
        }
    
        private Object getFieldValue(Object object, String fieldName) {
            Field field = null;
            for (Class<?> clazz=object.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {
                try {
                    field = clazz.getDeclaredField(fieldName);
                    if (field != null){
                        field.setAccessible(true);
                        break;
                    }
    
                } catch (NoSuchFieldException e) {
                    //這里不用做處理,子類(lèi)沒(méi)有該字段可能對(duì)應(yīng)的父類(lèi)有,都沒(méi)有就返回null。
                }
            }
            try {
                return field.get(object);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private void setFieldValue(Object object, String fieldName, Object value) {
            Field field = null;
            for (Class<?> clazz=object.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {
                try {
                    field = clazz.getDeclaredField(fieldName);
                    if (field != null){
                        field.setAccessible(true);
                        try {
                            field.set(object,value);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                        break;
                    }
    
                } catch (NoSuchFieldException e) {
                    //這里不用做處理,子類(lèi)沒(méi)有該字段可能對(duì)應(yīng)的父類(lèi)有,都沒(méi)有就返回null。
                }
            }
        }
    
        public String assemSql(String oldSql, RowBounds rowBounds) throws Exception {
            String sql = oldSql + " limit " + rowBounds.getOffset() + "," + rowBounds.getLimit();
            // 這兩步是必須的,因?yàn)樵谇懊嬷脫Q好sql語(yǔ)句以后,實(shí)際的結(jié)果集就是我們想要的所以offset和limit必須重置為初始值
            setFieldValue(rowBounds, "offset", RowBounds.NO_ROW_OFFSET);
            setFieldValue(rowBounds, "limit", RowBounds.NO_ROW_LIMIT);
            return sql;
        }
    
    }
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容