自定義mybatis攔截器實(shí)現(xiàn)分頁(yè)

原理:

mybatis提供了攔截器功能夸浅,我們可以對(duì)Executor超升,StatementHandler尖昏,ParameterHandler串绩,ResultSetHandler進(jìn)行攔截,也就是代理枉圃。

實(shí)現(xiàn):

分頁(yè)需要提供兩個(gè)數(shù)據(jù)功茴,一是偏移量,行數(shù)孽亲。在mysql數(shù)據(jù)庫(kù)中我們通過(guò)limit offset坎穿,rows實(shí)現(xiàn)分頁(yè)。
返回的結(jié)果返劲,我們需要總數(shù)玲昧,總頁(yè)數(shù)犯祠,當(dāng)前頁(yè)數(shù),結(jié)果集來(lái)映射我們的前端顯示酌呆。
我們通過(guò)攔截Executor來(lái)實(shí)現(xiàn),并且制定攔截的方法是query方法搔耕。

  1. 第一步:區(qū)分出分頁(yè)方法隙袁,怎么實(shí)現(xiàn)在不改變?nèi)魏未a的情況下將分頁(yè)區(qū)別出來(lái),首先弃榨,我們肯定需要知道哪些方法是分頁(yè)菩收,我們通過(guò)定義統(tǒng)一的方法后綴名來(lái)區(qū)分。
  2. 第二步:查詢出總數(shù)鲸睛,我們需要在原來(lái)sql的基礎(chǔ)上查詢出總數(shù)是多少娜饵,在這里我們通過(guò)映射對(duì)象,調(diào)用Executor的query方法來(lái)獲得官辈。
  3. 第三步:改造sql箱舞,變成分頁(yè)的sql語(yǔ)句。
  4. 第四步:執(zhí)行語(yǔ)句拳亿。
    在這里有些地方并沒(méi)有提供直接的修改方法晴股,所以只能通過(guò)替換的形式,代碼冗余肺魁。
原理理解

在這其中有幾個(gè)關(guān)鍵的類

  1. MappedStatement
  2. BoundSql
  3. Executor
MappedStatement

這個(gè)就是我們mapper文件中對(duì)應(yīng)的一個(gè)select/update/delete節(jié)點(diǎn)电湘。

public final class MappedStatement {
   private String resource;//mapper配置文件名,如:UserMapper.xml
   private Configuration configuration;//全局配置
   private String id;//節(jié)點(diǎn)的id屬性加命名空間,如:com.lucky.mybatis.dao.UserMapper.selectByExample
   private Integer fetchSize;
   private Integer timeout;//超時(shí)時(shí)間
   private StatementType statementType;//操作SQL的對(duì)象的類型
   private ResultSetType resultSetType;//結(jié)果類型
   private SqlSource sqlSource;//sql語(yǔ)句
   private Cache cache;//緩存
   private ParameterMap parameterMap;
   private List<ResultMap> resultMaps;
   private boolean flushCacheRequired;
   private boolean useCache;//是否使用緩存鹅经,默認(rèn)為true
   private boolean resultOrdered;//結(jié)果是否排序
   private SqlCommandType sqlCommandType;//sql語(yǔ)句的類型寂呛,如select、update瘾晃、delete贷痪、insert
   private KeyGenerator keyGenerator;
   private String[] keyProperties;
   private String[] keyColumns;
   private boolean hasNestedResultMaps;
   private String databaseId;//數(shù)據(jù)庫(kù)ID
   private Log statementLog;
   private LanguageDriver lang;
   private String[] resultSets;

通過(guò)mapperStatement我們可以獲得sql,方法名酗捌,參數(shù)呢诬,結(jié)果類型,這些都是我們后面需要用到的胖缤。

BoundSql

BoundSql保存了sql執(zhí)行sql語(yǔ)句所需要的所有參數(shù)尚镰,sql語(yǔ)句,傳入的參數(shù)

public class BoundSql {

  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Object parameterObject;
  private final Map<String, Object> additionalParameters;
  private final MetaObject metaParameters;
Executor

執(zhí)行器哪廓,所有的語(yǔ)句都通過(guò)這個(gè)執(zhí)行器執(zhí)行狗唉。我們下面主要的攔截對(duì)象,下面是我們要攔截的方法涡真。

 <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
思路

有了上面的對(duì)象分俯,我們也可以構(gòu)建自己的查詢肾筐,對(duì)于分頁(yè)查詢,我們說(shuō)先需要獲得總數(shù)量缸剪,這里我們需要在原有的基礎(chǔ)上構(gòu)建一個(gè)新的MappedStatement,修改sql和返回結(jié)果類型吗铐。對(duì)于分頁(yè)查詢,我們只需要在原來(lái)的基礎(chǔ)上修改sql杏节,但是MappedStatement并沒(méi)喲支持直接修改sql的方法唬渗,我們只能構(gòu)建新的MappedStatement替換原來(lái)的MappedStatement。

代碼實(shí)現(xiàn):

繼承Interceptor接口奋渔。添加@Interceptor注解镊逝。在@Signature 注解中指定攔截的類型,方法嫉鲸,方法參數(shù)撑蒜。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Intercepts {
    Signature[] value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
    Class<?> type();

    String method();

    Class<?>[] args();
}

實(shí)現(xiàn)接口的三個(gè)方法。

public class PageInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return null;
    }

    @Override
    public Object plugin(Object o) {
        return null;
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

setProperties方法玄渗,有我們自定義提供方法的后綴座菠,和頁(yè)大小。

  public void setProperties(Properties properties)  {
        Integer o = Integer.valueOf((String) properties.get("pager.pageSize"));
        if(o==null){
            try {
                throw  new PageSizeNullException("pageSize can not be null");
            } catch (PageSizeNullException e) {
                e.printStackTrace();
            }
        }
        pageSize=o;
        String o1 = (String) properties.get("pager.pageFlag");
        if(o1==null){
            try {
                throw  new PageFlagNullException("pageFlag can not be null.");
            } catch (PageFlagNullException e) {
                e.printStackTrace();
            }
        }
        PAGE_FLAG=o1;

    }

intercept方法捻爷。參數(shù)Invocation辈灼,我們通過(guò)斷點(diǎn)來(lái)看一下。target是Executor執(zhí)行器也榄,我們?cè)赼rgs中可以獲得MappedStatement映射對(duì)象巡莹,UserPager參數(shù),RowBounds甜紫。


image.png

第一步:獲得總數(shù)降宅。

  /**
     * 構(gòu)造count查詢語(yǔ)句,創(chuàng)建新的boundSql囚霸,創(chuàng)建新的mappedStatement腰根,再通過(guò)executor執(zhí)行查詢
     * @param executor
     * @param mappedStatement
     * @param parameter
     * @param boundSql
     * @param resultHandler
     * @return
     * @throws SQLException
     */
 public Long getTotalCount(Executor executor,MappedStatement mappedStatement,Object parameter,BoundSql boundSql,ResultHandler resultHandler) throws SQLException {
       String sql = boundSql.getSql();
        StringBuffer stringBuffer  = new StringBuffer();
        stringBuffer.append("select count(1) from (").append(sql).append(") as temp");
        BoundSql newboundSql = new BoundSql(mappedStatement.getConfiguration(),stringBuffer.toString(),boundSql.getParameterMappings(),parameter);
        MappedStatement mappedStatement1 = buildCountMappedStatement(mappedStatement, mappedStatement.getId()+COUNT_FLAG);
        CacheKey cacheKey = executor.createCacheKey(mappedStatement1, parameter, RowBounds.DEFAULT, boundSql);
        List query = executor.query(mappedStatement1, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, newboundSql);
        return (Long)query.get(0);
    }
 /**
     * 創(chuàng)建新的mappedStatement,添加結(jié)果映射
     * @param mappedStatement
     * @param id
     * @return
     */
    public MappedStatement buildCountMappedStatement(MappedStatement mappedStatement,String id){
            MappedStatement.Builder builder = new MappedStatement.Builder(mappedStatement.getConfiguration(),id,mappedStatement.getSqlSource(),mappedStatement.getSqlCommandType());

            List<ResultMap> resultMapList = new ArrayList<>();
            ResultMap.Builder resultMap =  new ResultMap.Builder(mappedStatement.getConfiguration(),id,Long.class,DEFAULT_LIST_RESULTMAPPING);
            resultMapList.add(resultMap.build());
            builder.resultMaps(resultMapList);
            return builder.build();
        }

獲得總數(shù)之后拓型,開(kāi)始構(gòu)建分頁(yè)查詢,我們只需要在原來(lái)的基礎(chǔ)之上修改sql就行了额嘿,但是并沒(méi)有可以直接修改sql的方法,所以我們只能通過(guò)替換mappedStatement的方式劣挫。mybatis提供了一個(gè)類似反射的工具M(jìn)etaObject册养。

 /**
     * 利用mybatis提供的反射工具,將sql注入到mappedStatement中去
     * @param invocation
     * @param sql
     */
    public void resetSql2Invocation(Invocation invocation,String sql){
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatements = (MappedStatement) args[0];
        Object parameter= args[1];
        MappedStatement mappedStatement = buildMappedStatement(mappedStatements, mappedStatements.getBoundSql(parameter));
        MetaObject metaObject = MetaObject.forObject(mappedStatement, OBJECT_FACTORY, OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
        metaObject.setValue("sqlSource.boundSql.sql",sql);
        args[0]=mappedStatement;
    }

    /**
     * 構(gòu)建新的mappedStatement
     * @param mappedStatement
     * @param boundSql
     * @return
     */
    public MappedStatement buildMappedStatement(MappedStatement mappedStatement,BoundSql boundSql){
        MappedStatement.Builder builder = new MappedStatement.Builder(mappedStatement.getConfiguration(),mappedStatement.getId(),new BoundSqlSource(boundSql),mappedStatement.getSqlCommandType());
       setProperties(builder,mappedStatement);
        return builder.build();
    }

至此压固,完成了分頁(yè)的實(shí)現(xiàn)球拦,我們看一下結(jié)果。


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市坎炼,隨后出現(xiàn)的幾起案子愧膀,更是在濱河造成了極大的恐慌,老刑警劉巖谣光,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件檩淋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡萄金,警方通過(guò)查閱死者的電腦和手機(jī)狼钮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)捡絮,“玉大人,你說(shuō)我怎么就攤上這事莲镣「N龋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵瑞侮,是天一觀的道長(zhǎng)的圆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)半火,這世上最難降的妖魔是什么越妈? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮钮糖,結(jié)果婚禮上梅掠,老公的妹妹穿的比我還像新娘。我一直安慰自己店归,他們只是感情好阎抒,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著消痛,像睡著了一般且叁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秩伞,一...
    開(kāi)封第一講書(shū)人閱讀 49,985評(píng)論 1 291
  • 那天逞带,我揣著相機(jī)與錄音,去河邊找鬼纱新。 笑死展氓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的怒炸。 我是一名探鬼主播带饱,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了勺疼?” 一聲冷哼從身側(cè)響起教寂,我...
    開(kāi)封第一講書(shū)人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎执庐,沒(méi)想到半個(gè)月后酪耕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡轨淌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年迂烁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片递鹉。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盟步,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出躏结,到底是詐尸還是另有隱情却盘,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布媳拴,位于F島的核電站黄橘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏屈溉。R本人自食惡果不足惜塞关,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望子巾。 院中可真熱鬧帆赢,春花似錦、人聲如沸线梗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)缠导。三九已至廉羔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間僻造,已是汗流浹背憋他。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留髓削,地道東北人竹挡。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像立膛,于是被迫代替她去往敵國(guó)和親揪罕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子梯码,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

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