MyBatis之plugin

MyBatis攔截器介紹

MyBatis 允許你在已映射語(yǔ)句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用。默認(rèn)情況下,MyBatis 允許使用插件來攔截的方法調(diào)用包括:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

StatementHandler (prepare, parameterize, batch, update, query)

我們看到了可以攔截Executor接口的部分方法唇跨,比如update,query,commit,rollback等方法樊零,還有其他接口的一些方法等。

總體概括為:

攔截執(zhí)行器的方法

攔截參數(shù)的處理

攔截結(jié)果集的處理

攔截Sql語(yǔ)法構(gòu)建的處理

MyBatis攔截器使用(分頁(yè)插件)

Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class PageHelper implements Interceptor {
    private static final Logger logger = Logger.getLogger(PageHelper.class);

    public static final ThreadLocal<Page> localPage = new ThreadLocal<Page>();

    /**
     * 開始分頁(yè)
     *
     * @param pageNum
     * @param pageSize
     */
    public static void startPage(int pageNum, int pageSize) {
        localPage.set(new Page(pageNum, pageSize));
    }

    /**
     * 結(jié)束分頁(yè)并返回結(jié)果孽文,該方法必須被調(diào)用驻襟,否則localPage會(huì)一直保存下去,直到下一次startPage
     *
     * @return
     */
    public static Page endPage() {
        Page page = localPage.get();
        localPage.remove();
        return page;
    }

    public Object intercept(Invocation invocation) throws Throwable {
        if (localPage.get() == null) {
            return invocation.proceed();
        }
        if (invocation.getTarget() instanceof StatementHandler) {
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
            // 分離代理對(duì)象鏈(由于目標(biāo)類可能被多個(gè)攔截器攔截芋哭,從而形成多次代理塑悼,通過下面的兩次循環(huán)
            // 可以分離出最原始的的目標(biāo)類)
            while (metaStatementHandler.hasGetter("h")) {
                Object object = metaStatementHandler.getValue("h");
                metaStatementHandler = SystemMetaObject.forObject(object);
            }
            // 分離最后一個(gè)代理對(duì)象的目標(biāo)類
            while (metaStatementHandler.hasGetter("target")) {
                Object object = metaStatementHandler.getValue("target");
                metaStatementHandler = SystemMetaObject.forObject(object);
            }
            MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
            //分頁(yè)信息if (localPage.get() != null) {
            Page page = localPage.get();
            BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
            // 分頁(yè)參數(shù)作為參數(shù)對(duì)象parameterObject的一個(gè)屬性
            String sql = boundSql.getSql();
            // 重寫sql
            String pageSql = buildPageSql(sql, page);
            //重寫分頁(yè)sql
            metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
            Connection connection = (Connection) invocation.getArgs()[0];
            // 重設(shè)分頁(yè)參數(shù)里的總頁(yè)數(shù)等
            setPageParameter(sql, connection, mappedStatement, boundSql, page);
            // 將執(zhí)行權(quán)交給下一個(gè)攔截器
            return invocation.proceed();
        } else if (invocation.getTarget() instanceof ResultSetHandler) {
            Object result = invocation.proceed();
            Page page = localPage.get();
            page.setResult((List) result);
            return result;
        }
        return null;
    }

    /**
     * 只攔截這兩種類型的
     * <br>StatementHandler
     * <br>ResultSetHandler
     *
     * @param target
     * @return
     */
    public Object plugin(Object target) {
        if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    public void setProperties(Properties properties) {
    }

    /**
     * 修改原SQL為分頁(yè)SQL
     *
     * @param sql
     * @param page
     * @return
     */
    private String buildPageSql(String sql, Page page) {
        StringBuilder pageSql = new StringBuilder(100);
        pageSql.append(sql);
        pageSql.append(" limit " + page.getStartRow() + "," + page.getPageSize());
        return pageSql.toString();
    }

    /**
     * 獲取總記錄數(shù)
     *
     * @param sql
     * @param connection
     * @param mappedStatement
     * @param boundSql
     * @param page
     */
    private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,
                                  BoundSql boundSql, Page page) {
        // 記錄總記錄數(shù)
        String countSql = "select count(0) from (" + sql + ") as total";
        PreparedStatement countStmt = null;
        ResultSet rs = null;
        try {
            countStmt = connection.prepareStatement(countSql);
            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());
            setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
            rs = countStmt.executeQuery();
            int totalCount = 0;
            if (rs.next()) {
                totalCount = rs.getInt(1);
            }
            page.setTotal(totalCount);
            int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);
            page.setPages(totalPage);
        } catch (SQLException e) {
            logger.error("Ignore this exception", e);
        } finally {
            try {
                rs.close();
            } catch (SQLException e) {
                logger.error("Ignore this exception", e);
            }
            try {
                countStmt.close();
            } catch (SQLException e) {
                logger.error("Ignore this exception", e);
            }
        }
    }

    /**
     * 代入?yún)?shù)值
     *
     * @param ps
     * @param mappedStatement
     * @param boundSql
     * @param parameterObject
     * @throws SQLException
     */
    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
                               Object parameterObject) throws SQLException {
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler.setParameters(ps);
    }

    /**
     * Description: 分頁(yè)
     * Author: liuzh
     * Update: liuzh(2014-04-16 10:56)
     */
    @Data
    public static class Page<E> {
        private int pageNum;
        private int pageSize;
        private int startRow;
        private int endRow;
        private long total;
        private int pages;
        private List<E> result;

        public Page(int pageNum, int pageSize) {
            this.pageNum = pageNum;
            this.pageSize = pageSize;
            this.startRow = pageNum > 0 ? (pageNum - 1) * pageSize : 0;
            this.endRow = pageNum * pageSize;
        }
    }
}
//configuration.xml添加配置
<plugins>
        <plugin interceptor="mybatis.plugin.PageHelper"></plugin>
    </plugins>

源碼分析

 //將攔截器set到configuration
 private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
  //獲取SqlSession(Configuration類下)
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  //獲取SqlSession
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //獲取executor,接下來是具體代碼
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
   public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //添加攔截器
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
  //InterceptorChain
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
  //然后走自己實(shí)現(xiàn)的代碼楷掉,如果這里攔截的是Executor則調(diào)用plugin()方法
  public Object plugin(Object target) {
        if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

  //生成代理并且返回
  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;
  }

  //如果被代理則Plugin類invoke()
  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)) {
        //自己實(shí)現(xiàn)的intercept()
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

注意:

在每個(gè)攔截器的intercept方法內(nèi),最后一個(gè)語(yǔ)句一定是return invocation.proceed()霞势。invocation.proceed()只是簡(jiǎn)單的調(diào)用了下target的對(duì)應(yīng)方法烹植,如果target還是個(gè)代理,就又回到了上面的Plugin.invoke方法了愕贡。這樣就形成了攔截器的調(diào)用鏈推進(jìn)草雕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市固以,隨后出現(xiàn)的幾起案子墩虹,更是在濱河造成了極大的恐慌嘱巾,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诫钓,死亡現(xiàn)場(chǎng)離奇詭異旬昭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)菌湃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門问拘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惧所,你說我怎么就攤上這事骤坐。” “怎么了下愈?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵纽绍,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我势似,道長(zhǎng)拌夏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任叫编,我火速辦了婚禮辖佣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搓逾。我一直安慰自己卷谈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布霞篡。 她就那樣靜靜地躺著世蔗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪朗兵。 梳的紋絲不亂的頭發(fā)上污淋,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音余掖,去河邊找鬼寸爆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛盐欺,可吹牛的內(nèi)容都是我干的赁豆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼冗美,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼魔种!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粉洼,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤节预,失蹤者是張志新(化名)和其女友劉穎叶摄,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體安拟,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蛤吓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了去扣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柱衔。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖愉棱,靈堂內(nèi)的尸體忽然破棺而出唆铐,到底是詐尸還是另有隱情,我是刑警寧澤奔滑,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布艾岂,位于F島的核電站,受9級(jí)特大地震影響朋其,放射性物質(zhì)發(fā)生泄漏王浴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一梅猿、第九天 我趴在偏房一處隱蔽的房頂上張望氓辣。 院中可真熱鬧,春花似錦袱蚓、人聲如沸钞啸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)体斩。三九已至,卻和暖如春颖低,著一層夾襖步出監(jiān)牢的瞬間絮吵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工忱屑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹬敲,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓莺戒,卻偏偏與公主長(zhǎng)得像粱栖,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子脏毯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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

  • MyBatis提供了一種插件(plugin)的功能,雖然叫做插件幔崖,但其實(shí)這是攔截器功能食店。那么攔截器攔截MyBati...
    七寸知架構(gòu)閱讀 3,253評(píng)論 3 54
  • 1. 簡(jiǎn)介 1.1 什么是 MyBatis 渣淤? MyBatis 是支持定制化 SQL、存儲(chǔ)過程以及高級(jí)映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,520評(píng)論 0 4
  • 11 MyBatis一級(jí)緩存實(shí)現(xiàn)# 11.1 什么是一級(jí)緩存吉嫩? 為什么使用一級(jí)緩存价认?## 每當(dāng)我們使用MyBati...
    七寸知架構(gòu)閱讀 10,847評(píng)論 12 143
  • 記錄是一種精神,是加深理解最好的方式之一自娩。 最近看了下Mybatis的源碼用踩,分析了Mybatis插件的實(shí)現(xiàn)方式,在...
    曹金桂閱讀 17,953評(píng)論 15 52
  • 當(dāng)我造男人時(shí)忙迁,我造他并將生命的氣息氣吹入他鼻里脐彩。 然而,在創(chuàng)造你——女人時(shí)姊扔,我是在賦予男人生命后才造你惠奸, 因你的精...
    岳曉晴閱讀 386評(píng)論 0 2