<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          只要五分鐘,徹底搞懂MyBatis插件原理及PageHelper原理

          共 6147字,需瀏覽 13分鐘

           ·

          2022-02-25 12:38

          點(diǎn)擊上方“程序員大白”,選擇“星標(biāo)”公眾號(hào)

          重磅干貨,第一時(shí)間送達(dá)

          前言

          提到插件,相信大家都知道,插件的存在主要是用來(lái)改變或者增強(qiáng)原有的功能,MyBatis中也一樣。然而如果我們對(duì)MyBatis的工作原理不是很清楚的話,最好不要輕易使用插件,否則的話如果因?yàn)槭褂貌寮?dǎo)致了底層工作邏輯被改變,很可能會(huì)出現(xiàn)很多意料之外的問(wèn)題。

          本文主要會(huì)介紹MyBatis插件的使用及其實(shí)現(xiàn)原理,相信讀完本文,我們也可以寫(xiě)出自己的PageHelper分頁(yè)插件了。

          MyBatis中插件是如何實(shí)現(xiàn)的

          在MyBatis中插件式通過(guò)攔截器來(lái)實(shí)現(xiàn)的,那么既然是通過(guò)攔截器來(lái)實(shí)現(xiàn)的,就會(huì)有一個(gè)問(wèn)題,哪些對(duì)象才允許被攔截呢?

          回想前面我們介紹Executor的文章中提到,真正執(zhí)行Sql的是四大對(duì)象:ExecutorStatementHandlerParameterHandlerResultSetHandler。而MyBatis的插件正是基于攔截這四大對(duì)象來(lái)實(shí)現(xiàn)的。

          需要注意的是,雖然我們可以攔截這四大對(duì)象,但是并不是這四大對(duì)象中的所有方法都能被攔截,下面就是官網(wǎng)提供的可攔截的對(duì)象和方法匯總:

          MyBatis插件的使用

          首先我們先來(lái)通過(guò)一個(gè)例子來(lái)看看如何使用插件。

          1、首先建立一個(gè)MyPlugin實(shí)現(xiàn)接口Interceptor,然后重寫(xiě)其中的三個(gè)方法(注意,這里必須要實(shí)現(xiàn)Interceptor接口,否則無(wú)法被攔截)。

          package?com.lonelyWolf.mybatis.plugin;

          import?org.apache.ibatis.executor.Executor;
          import?org.apache.ibatis.mapping.MappedStatement;
          import?org.apache.ibatis.plugin.*;
          import?org.apache.ibatis.session.ResultHandler;
          import?org.apache.ibatis.session.RowBounds;
          import?java.util.Properties;

          @Intercepts({@Signature(type?=?Executor.class,method?=?"query",args?=?{MappedStatement.class,Object.class,?RowBounds.class,?ResultHandler.class})})
          public?class?MyPlugin?implements?Interceptor?
          {

          ????/**
          ?????*?這個(gè)方法會(huì)直接覆蓋原有方法
          ?????*?@param?invocation
          ?????*?@return
          ?????*?@throws?Throwable
          ?????*/

          ????@Override
          ????public?Object?intercept(Invocation?invocation)?throws?Throwable?{
          ????????System.out.println("成功攔截了Executor的query方法,在這里我可以做點(diǎn)什么");
          ????????return?invocation.proceed();//調(diào)用原方法
          ????}

          ????@Override
          ????public?Object?plugin(Object?target)?{
          ????????return?Plugin.wrap(target,this);//把被攔截對(duì)象生成一個(gè)代理對(duì)象
          ????}

          ????@Override
          ????public?void?setProperties(Properties?properties)?{//可以自定義一些屬性
          ????????System.out.println("自定義屬性:userName->"?+?properties.getProperty("userName"));
          ????}
          }

          @Intercepts是聲明當(dāng)前類是一個(gè)攔截器,后面的@Signature是標(biāo)識(shí)需要攔截的方法簽名,通過(guò)以下三個(gè)參數(shù)來(lái)確定

          • type:被攔截的類名。
          • method:被攔截的方法名
          • args:標(biāo)注方法的參數(shù)類型

          2、我們還需要在mybatis-config中配置好插件。

          <plugins>
          ????<plugin?interceptor="com.lonelyWolf.mybatis.plugin.MyPlugin">
          ??????<property?name="userName"?value="張三"/>
          ????plugin>
          plugins>

          這里如果配置了property屬性,那么我們可以在setProperties獲取到。

          完成以上兩步,我們就完成了一個(gè)插件的配置了,接下來(lái)我們運(yùn)行一下:

          可以看到,setProperties方法在加載配置文件階段就會(huì)被執(zhí)行了。

          MyBatis插件實(shí)現(xiàn)原理

          接下來(lái)讓我們分析一下從插件的加載到初始化到運(yùn)行整個(gè)過(guò)程的實(shí)現(xiàn)原理。

          插件的加載

          既然插件需要在配置文件中進(jìn)行配置,那么肯定就需要進(jìn)行解析,我們看看插件式如何被解析的。我們進(jìn)入XMLConfigBuilder類看看

          解析出來(lái)之后會(huì)將插件存入InterceptorChain對(duì)象的list屬性

          看到InterceptorChain我們是不是可以聯(lián)想到,MyBatis的插件就是通過(guò)責(zé)任鏈模式實(shí)現(xiàn)的。

          插件如何進(jìn)行攔截

          既然插件類已經(jīng)被加載到配置文件了,那么接下來(lái)就有一個(gè)問(wèn)題了,插件類何時(shí)會(huì)被攔截我們需要攔截的對(duì)象呢?

          其實(shí)插件的攔截是和對(duì)象有關(guān)的,不同的對(duì)象進(jìn)行攔截的時(shí)間也會(huì)不一致,接下來(lái)我們就逐一分析一下。

          攔截Executor對(duì)象

          我們知道,SqlSession對(duì)象是通過(guò)openSession()方法返回的,而Executor又是屬于SqlSession內(nèi)部對(duì)象,所以讓我們跟隨openSession方法去看一下Executor對(duì)象的初始化過(guò)程。

          可以看到,當(dāng)初始化完成Executor之后,會(huì)調(diào)用interceptorChain的pluginAll方法,pluginAll方法本身非常簡(jiǎn)單,就是把我們存到list中的插件進(jìn)行循環(huán),并調(diào)用Interceptor對(duì)象的plugin方法:

          再次點(diǎn)擊進(jìn)去:

          到這里我們是不是發(fā)現(xiàn)很熟悉,沒(méi)錯(cuò),這就是我們上面示例中重寫(xiě)的方法,而plugin方法是接口中的一個(gè)默認(rèn)方法。

          這個(gè)方法是關(guān)鍵,我們進(jìn)去看看:

          可以看到這個(gè)方法的邏輯也很簡(jiǎn)單,但是需要注意的是MyBatis插件是通過(guò)JDK動(dòng)態(tài)代理來(lái)實(shí)現(xiàn)的,而JDK動(dòng)態(tài)代理的條件就是被代理對(duì)象必須要有接口,這一點(diǎn)和Spring中不太一樣,Spring中是如果有接口就采用JDK動(dòng)態(tài)代理,沒(méi)有接口就是用CGLIB動(dòng)態(tài)代理。

          正因?yàn)镸yBatis的插件只使用了JDK動(dòng)態(tài)代理,所以我們上面才強(qiáng)調(diào)了一定要實(shí)現(xiàn)Interceptor接口。

          而代理之后匯之星Plugin的invoke方法,我們最后再來(lái)看看invoke方法:

          而最終執(zhí)行的intercept方法,就是我們上面示例中重寫(xiě)的方法。

          其他對(duì)象插件解析

          接下來(lái)我們?cè)倏纯?code style="font-size: 14px;border-radius: 4px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">StatementHandler,StatementHandler是在Executor中的doQuery方法創(chuàng)建的,其實(shí)這個(gè)原理就是一樣的了,找到初始化StatementHandler對(duì)象的方法:

          進(jìn)去之后里面執(zhí)行的也是pluginAll方法:

          其他兩個(gè)對(duì)象就不在舉例了,其實(shí)搜一下全局就很明顯了:

          PS:

          四個(gè)對(duì)象初始化的時(shí)候都會(huì)調(diào)用pluginAll來(lái)進(jìn)行判定是否有被代理。

          插件執(zhí)行流程

          下面就是實(shí)現(xiàn)了插件之后的執(zhí)行時(shí)序圖:

          假如一個(gè)對(duì)象被代理很多次

          一個(gè)對(duì)象是否可以被多個(gè)代理對(duì)象進(jìn)行代理?也就是說(shuō)同一個(gè)對(duì)象的同一個(gè)方法是否可以被多個(gè)攔截器進(jìn)行攔截?

          答案是肯定的,因?yàn)楸淮韺?duì)象是被加入到list,所以我們配置在最前面的攔截器最先被代理,但是執(zhí)行的時(shí)候卻是最外層的先執(zhí)行。

          具體點(diǎn):

          假如依次定義了三個(gè)插件:插件A,插件B和插件C。

          那么List中就會(huì)按順序存儲(chǔ):插件A,插件B和插件C,而解析的時(shí)候是遍歷list,所以解析的時(shí)候也是按照:插件A,插件B和插件C的順序,但是執(zhí)行的時(shí)候就要反過(guò)來(lái)了,執(zhí)行的時(shí)候是按照:插件C,插件B和插件A的順序進(jìn)行執(zhí)行。

          PageHelper插件的使用

          上面我們了解了在MyBatis中的插件是如何定義以及MyBatis中是如何處理插件的,接下來(lái)我們就以經(jīng)典分頁(yè)插件PageHelper為例來(lái)進(jìn)一步加深理解。

          首先我們看看PageHelper的用法:

          package?com.lonelyWolf.mybatis;

          import?com.alibaba.fastjson.JSONObject;
          import?com.github.pagehelper.Page;
          import?com.github.pagehelper.PageHelper;
          import?com.github.pagehelper.PageInfo;
          import?com.lonelyWolf.mybatis.mapper.UserMapper;
          import?com.lonelyWolf.mybatis.model.LwUser;
          import?org.apache.ibatis.executor.result.DefaultResultHandler;
          import?org.apache.ibatis.io.Resources;
          import?org.apache.ibatis.session.ResultHandler;
          import?org.apache.ibatis.session.SqlSession;
          import?org.apache.ibatis.session.SqlSessionFactory;
          import?org.apache.ibatis.session.SqlSessionFactoryBuilder;

          import?java.io.IOException;
          import?java.io.InputStream;
          import?java.util.List;

          public?class?MyBatisByPageHelp?{
          ????public?static?void?main(String[]?args)?throws?IOException?{
          ????????String?resource?=?"mybatis-config.xml";
          ????????//讀取mybatis-config配置文件
          ????????InputStream?inputStream?=?Resources.getResourceAsStream(resource);
          ????????//創(chuàng)建SqlSessionFactory對(duì)象
          ????????SqlSessionFactory?sqlSessionFactory?=?new?SqlSessionFactoryBuilder().build(inputStream);
          ????????//創(chuàng)建SqlSession對(duì)象
          ????????SqlSession?session?=?sqlSessionFactory.openSession();

          ????????PageHelper.startPage(0,10);
          ????????UserMapper?userMapper?=?session.getMapper(UserMapper.class);
          ????????List?userList?=?userMapper.listAllUser();
          ????????PageInfo?pageList?=?new?PageInfo<>(userList);
          ????????System.out.println(null?==?pageList???"":?JSONObject.toJSONString(pageList));

          ????}
          }

          輸出如下結(jié)果:

          可以看到對(duì)象已經(jīng)被分頁(yè),那么這是如何做到的呢?

          PageHelper插件原理

          我們上面提到,要實(shí)現(xiàn)插件必須要實(shí)現(xiàn)MyBatis提供的Interceptor接口,所以我們?nèi)フ乙幌拢l(fā)現(xiàn)PageHeler實(shí)現(xiàn)了Interceptor

          經(jīng)過(guò)上面的介紹這個(gè)類應(yīng)該一眼就能看懂,我們關(guān)鍵要看看SqlUtil的intercept方法做了什么:

          這個(gè)方法的邏輯比較多,因?yàn)橐紤]到不同的數(shù)據(jù)庫(kù)方言的問(wèn)題,所以會(huì)有很多判斷,我們主要是關(guān)注PageHelper在哪里改寫(xiě)了sql語(yǔ)句,上圖中的紅框就是改寫(xiě)了sql語(yǔ)句的地方:

          這里面會(huì)獲取到一個(gè)Page對(duì)象,然后在愛(ài)寫(xiě)sql的時(shí)候也會(huì)將一些分頁(yè)參數(shù)設(shè)置到Page對(duì)象,我們看看Page對(duì)象是從哪里獲取的:

          我們看到對(duì)象是從LOCAL_PAGE對(duì)象中獲取的,這個(gè)又是什么呢?

          這是一個(gè)本地線程池變量,那么這里面的Page又是什么時(shí)候存進(jìn)去的呢?

          這就要回到我們的示例上了,分頁(yè)的開(kāi)始必須要調(diào)用:

          PageHelper.startPage(0,10);

          這里就會(huì)構(gòu)建一個(gè)Page對(duì)象,并設(shè)置到ThreadLocal內(nèi)。

          為什么PageHelper只對(duì)startPage后的第一條select語(yǔ)句有效

          這個(gè)其實(shí)也很簡(jiǎn)單哈,但是可能會(huì)有人有這個(gè)以為,我們還是要回到上面的intercept方法:

          在finally內(nèi)把ThreadLocal中的分頁(yè)數(shù)據(jù)給清除掉了,所以只要執(zhí)行一次查詢語(yǔ)句就會(huì)清除分頁(yè)信息,故而后面的select語(yǔ)句自然就無(wú)效了。

          不通過(guò)插件能否改變MyBatis的核心行為

          上面我們介紹了通過(guò)插件來(lái)改變MyBatis的核心行為,那么不通過(guò)插件是否也可以實(shí)現(xiàn)呢?

          答案是肯定的,官網(wǎng)中提到,我們可以通過(guò)覆蓋配置類來(lái)實(shí)現(xiàn)改變MyBatis核心行為,也就是我們自己寫(xiě)一個(gè)類繼承Configuration類,然后實(shí)現(xiàn)其中的方法,最后構(gòu)建SqlSessionFactory對(duì)象的時(shí)候傳入自定義的Configuration方法:

          SqlSessionFactory?build(MyConfiguration)

          當(dāng)然,這種方法是非常不建議使用的,因?yàn)檫@種方式就相當(dāng)于在建房子的時(shí)候把地基抽出來(lái)重新建了,稍有不慎,房子就要塌了。

          總結(jié)

          本文主要會(huì)介紹MyBatis插件的使用及MyBatis其實(shí)現(xiàn)原理,最后我們也大致介紹了PageHelper插件的主要實(shí)現(xiàn)原理,相信讀完本文學(xué)會(huì)MyBatis插件原理之后,我們也可以寫(xiě)個(gè)簡(jiǎn)單的自己的PageHelper分頁(yè)插件了。

          來(lái)源:blog.csdn.net/zwx900102/article/

          details/108941441


          13個(gè)你一定要知道的PyTorch特性

          解讀:為什么要做特征歸一化/標(biāo)準(zhǔn)化?

          一文搞懂 PyTorch 內(nèi)部機(jī)制

          張一鳴:每個(gè)逆襲的年輕人,都具備的底層能力


          關(guān)


          學(xué)西學(xué)學(xué)運(yùn)營(yíng)護(hù)號(hào)樂(lè)質(zhì)結(jié)識(shí)關(guān)[]學(xué)習(xí)進(jìn)


          瀏覽 58
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大香蕉大香蕉在线 | 欧美成人精品一区二区特级毛片 | 91AV久久久 | 青娱乐成人在线视频 | 亚洲人成亚洲人成在线观看 |