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

        共 6147字,需瀏覽 13分鐘

         ·

        2022-02-25 12:38

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

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

        前言

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

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

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

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

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

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

        MyBatis插件的使用

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

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

        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è)方法會直接覆蓋原有方法
        ?????*?@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);//把被攔截對象生成一個(gè)代理對象
        ????}

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

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

        • 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è)插件的配置了,接下來我們運(yùn)行一下:

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

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

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

        插件的加載

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

        解析出來之后會將插件存入InterceptorChain對象的list屬性

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

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

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

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

        攔截Executor對象

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

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

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

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

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

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

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

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

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

        其他對象插件解析

        接下來我們再看看StatementHandler,StatementHandler是在Executor中的doQuery方法創(chuàng)建的,其實(shí)這個(gè)原理就是一樣的了,找到初始化StatementHandler對象的方法:

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

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

        PS:

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

        插件執(zhí)行流程

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

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

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

        答案是肯定的,因?yàn)楸淮韺ο笫潜患尤氲絣ist,所以我們配置在最前面的攔截器最先被代理,但是執(zhí)行的時(shí)候卻是最外層的先執(zhí)行。

        具體點(diǎn):

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

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

        PageHelper插件的使用

        上面我們了解了在MyBatis中的插件是如何定義以及MyBatis中是如何處理插件的,接下來我們就以經(jīng)典分頁插件PageHelper為例來進(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對象
        ????????SqlSessionFactory?sqlSessionFactory?=?new?SqlSessionFactoryBuilder().build(inputStream);
        ????????//創(chuàng)建SqlSession對象
        ????????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é)果:

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

        PageHelper插件原理

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

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

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

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

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

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

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

        PageHelper.startPage(0,10);

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

        為什么PageHelper只對startPage后的第一條select語句有效

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

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

        不通過插件能否改變MyBatis的核心行為

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

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

        SqlSessionFactory?build(MyConfiguration)

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

        總結(jié)

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

        來源: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)護(hù),質(zhì)結(jié),關(guān)[]學(xué)習(xí)進(jìn)!


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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 亚洲精品午夜精品 | 黑丝艹逼 | 中文字幕一区二区三区四虎在线 | 色国产在线 | 久久国产成人视频 |