MyBatis | MyBatis中使用插件追他、使用PageInterceptor插件坟募、自定義類型處理器

一岛蚤、插件原理

在四大對象創(chuàng)建的時候,有以下幾個特性:

  • 每個創(chuàng)建出來的對象不是直接返回的婿屹,而是interceptorChain.pluginAll(parameterHandler);
  • 該方法獲取到所有的Interceptor(插件需要實現(xiàn)的接口)灭美,調(diào)用interceptor.plugin(target);返回target包裝后的對象。
    public Object pluginAll(Object target) {
        Interceptor interceptor;
        for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
            interceptor = (Interceptor)var2.next();
        }

        return target;
    }

因此昂利,我們可以使用插件為目標對象創(chuàng)建一個代理對象届腐,類似于AOP(面向切面編程)。我們的插件可以為四大對象創(chuàng)建出代理對象蜂奸,代理對象就可以攔截到四大對象中每一個的執(zhí)行犁苏。


二、插件編寫(單個插件)

1扩所、編寫Interceptor的實現(xiàn)類

package com.cerr.dao;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
public class MyFirstPlugin implements Interceptor {

    //攔截目標對象的目標方法的執(zhí)行
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("intercept..."+invocation);
        //執(zhí)行目標方法
        Object proceed = invocation.proceed();
        //返回執(zhí)行后的返回值
        return proceed;
    }

    //包裝目標對象围详,為目標對象創(chuàng)建一個代理對象
    public Object plugin(Object target) {
        System.out.println("plugin..."+target);
        //借助這個方法來使用當前Interceptor包裝我們目標對象
        Object wrap = Plugin.wrap(target,this);
        //返回當前target創(chuàng)建的動態(tài)代理對象
        return wrap;
    }

    //將插件注冊時的property屬性設置進去
    public void setProperties(Properties properties) {
        System.out.println("插件配置的信息"+properties);

    }
}

2、使用@Intercepts注解完成插件的簽名

在編寫的插件類上標注@Intercepts祖屏,標注后的插件類如下:

package com.cerr.dao;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.util.Properties;

//完成插件簽名:告訴MyBatis當前插件來用攔截哪個對象的哪個方法
@Intercepts({
        @Signature(type = StatementHandler.class,method = "parameterize",
            args = java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor {

    //攔截目標對象的目標方法的執(zhí)行
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("intercept..."+invocation);
        //執(zhí)行目標方法
        Object proceed = invocation.proceed();
        //返回執(zhí)行后的返回值
        return proceed;
    }

    //包裝目標對象助赞,為目標對象創(chuàng)建一個代理對象
    public Object plugin(Object target) {
        System.out.println("plugin..."+target);
        //借助這個方法來使用當前Interceptor包裝我們目標對象
        Object wrap = Plugin.wrap(target,this);
        //返回當前target創(chuàng)建的動態(tài)代理對象
        return wrap;
    }

    //將插件注冊時的property屬性設置進去
    public void setProperties(Properties properties) {
        System.out.println("插件配置的信息"+properties);

    }
}

3、將寫好的插件注冊到全局配置文件中

在全局配置文件中使用<plugins>標簽來注冊:

    <plugins>
        <plugin interceptor="com.cerr.dao.MyFirstPlugin"></plugin>
    </plugins>

此時我們就定義了一個攔截StatementHandler對象的parameterize方法的攔截器袁勺。


三雹食、多個插件時的執(zhí)行順序

我們再編寫一個插件類MySecondPlugin,也是和上面的插件類一樣攔截的同一個對象的同一個方法期丰。代碼如下:

package com.cerr.dao;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.util.Properties;
@Intercepts({
        @Signature(type = StatementHandler.class,method = "parameterize",
                args = java.sql.Statement.class)
})
public class MySecondPlugin implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("MySecondPlugin..intercept:"+invocation);
        return invocation.proceed();
    }

    public Object plugin(Object target) {
        System.out.println("MySecondPlugin...plugin:"+target);
        return Plugin.wrap(target,this);
    }

    public void setProperties(Properties properties) {
    }
}

在全局配置文件中配置:

    <plugins>
        <plugin interceptor="com.cerr.dao.MyFirstPlugin"></plugin>
        <plugin interceptor="com.cerr.dao.MySecondPlugin"></plugin>
    </plugins>

配置后我們隨便找一個測試方法運行群叶,例如:

    @Test
    public void testSimple() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            List< Employee > list = mapper.selectByExample(null);
        }finally {
            session.close();
        }
    }

結(jié)果如圖1所示:


圖1:控制臺結(jié)果

從上圖可以看出,在生成代理對象時钝荡,是先創(chuàng)建第一個插件類的代理對象街立,再創(chuàng)建第二個插件類的代理對象;但是在攔截目標方法的時候埠通,則是先執(zhí)行第二個插件類赎离,再執(zhí)行第一個插件類。

因此我們可以得出以下結(jié)論:創(chuàng)建動態(tài)代理的時候端辱,是按照插件配置的順序?qū)訉觿?chuàng)建代理對象的蟹瘾。執(zhí)行目標方法的時候,按照逆序順序執(zhí)行掠手。

我們可以將該過程類比為如下的模型憾朴,首先創(chuàng)建的StatementHandler,然后再創(chuàng)建MyFirstPlugin代理對象喷鸽,然后再創(chuàng)建了MySecondPlugin代理對象众雷,其三者的關系是晚創(chuàng)建的包含早創(chuàng)建的,在執(zhí)行目標方法的時候自然而然是從外向里執(zhí)行。

圖2:動態(tài)代理模型


四砾省、使用PageInterceptor插件

1鸡岗、導包

需要兩個包,可以在GitHub上面下載:點此下載

圖3:下載這兩個jar包

2编兄、在全局配置文件中注冊PageInterceptor插件

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

3轩性、編碼

可以使用Page對象:

    @Test
    public void test() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            //第一個參數(shù)是頁碼,第二個是每頁的記錄數(shù)
            Page <Object> page = PageHelper.startPage(1,5);
            List <Employee> list = mapper.getEmps();
            System.out.println("當前頁碼:"+page.getPageNum());
            System.out.println("總記錄數(shù):"+page.getTotal());
            System.out.println("每頁的記錄數(shù):"+page.getPageSize());
            System.out.println("總頁碼:"+page.getPages());
        }finally {
            session.close();
        }
    }

可以使用Page對象來獲取關于分頁的數(shù)據(jù)狠鸳,例如當前頁碼揣苏、總記錄數(shù)等等。PageHelper.startPage(1,5)表示顯示第一頁件舵,然后每頁有5條記錄數(shù)卸察。

    @Test
    public void test() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            //第一個參數(shù)是頁碼,第二個是每頁的記錄數(shù)
            PageHelper.startPage(1,5);
            List <Employee> list = mapper.getEmps();
            PageInfo<Employee> info = new PageInfo <>(list);
            System.out.println("當前頁碼:"+info.getPageNum());
            System.out.println("總記錄數(shù):"+info.getTotal());
            System.out.println("每頁的記錄數(shù):"+info.getPageSize());
            System.out.println("總頁碼:"+info.getPages());
            System.out.println("是否第一頁:"+info.isIsFirstPage());
            System.out.println("是否最后一頁:"+info.isIsLastPage());
        }finally {
            session.close();
        }
    }

也可以使用PageInfo對象來獲取分頁的數(shù)據(jù)铅祸,跟上面代碼差不多坑质。


五、使用BatchExecutor進行批量操作

在通過SqlSessionFactory獲取SqlSession的時候傳入一個參數(shù)即可临梗,即:

SqlSessionFactory factory = getSqlSessionFactory();
//可以執(zhí)行批量操作的SqlSession
SqlSession session = factory.openSession(ExecutorType.BATCH);

設置了該參數(shù)之后涡扼,現(xiàn)在該SqlSession就是可以批量操作的了:

    @Test
    public void testBatch() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        //可以執(zhí)行批量操作的SqlSession
        SqlSession session = factory.openSession(ExecutorType.BATCH);
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            for (int i = 0;i < 10000;++i){
                mapper.addEmps(new Employee(null,"a","a","a"));
            }
            session.commit();
        }finally {
            session.close();
        }
    }

六、自定義TypeHandler來處理枚舉類型

我們在Employee類中添加一個EmpStatus字段盟庞,用來保存該類的狀態(tài)壳澳,該字段是一個枚舉類:

package com.cerr.mybatis;

public enum EmpStatus {
    LOGIN,LOGOUT,REMOVE;
}

MyBatis在處理枚舉類型的時候有兩個TypeHandler,一個是EnumTypeHandler茫经,另一個是EnumOrdinalTypeHandler

  • EnumTypeHandler:保存枚舉對象的時候默認保存的是枚舉對象的名字
  • EnumOrdinalTypeHandler:保存枚舉對象的時候默認保存的是枚舉對象的索引萎津。

我們現(xiàn)在想自己來處理枚舉類型卸伞,就需要自定義TypeHandler來實現(xiàn),自定義的類需要實現(xiàn)TypeHandler接口或者繼承BaseTypeHandler锉屈。

我們首先將EmpStatus來改造一下荤傲,因為我們想在保存進數(shù)據(jù)庫的時候存狀態(tài)碼,然后獲取的時候獲取的是該對象的信息颈渊,因此需要增加兩個字段msgcode遂黍,再增加對應的構(gòu)造方法、gettersetter方法后:

package com.cerr.mybatis;
public enum EmpStatus {
    LOGIN(100,"用戶登錄"),LOGOUT(200,"用戶登出"),REMOVE(300,"用戶不存在");
    private Integer code;
    private String msg;
    public static EmpStatus getEmpStatusByCode(Integer code){
        switch (code){
            case 100:
                return LOGIN;
            case 200:
                return LOGOUT;
            case 300:
                return REMOVE;
             default:
                 return LOGOUT;
        }
    }
    EmpStatus(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
}

1俊嗽、自定義TypeHandler類

package com.cerr.mybatis.dao;
import com.cerr.mybatis.EmpStatus;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
 * 實現(xiàn)TypeHandler接口或者繼承BaseTypeHandler
 */
public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus> {
    //定義數(shù)據(jù)如何保存到數(shù)據(jù)庫中
    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, EmpStatus empStatus, JdbcType jdbcType) throws SQLException {
        preparedStatement.setString(i,empStatus.getCode().toString());
    }

    @Override
    public EmpStatus getResult(ResultSet resultSet, String s) throws SQLException {
        //根據(jù)從數(shù)據(jù)庫中拿到的狀態(tài)碼返回枚舉對象
        int code = resultSet.getInt(s);
        return EmpStatus.getEmpStatusByCode(code);
    }

    @Override
    public EmpStatus getResult(ResultSet resultSet, int i) throws SQLException {
        //根據(jù)從數(shù)據(jù)庫中拿到的狀態(tài)碼返回枚舉對象
        int code = resultSet.getInt(i);
        return EmpStatus.getEmpStatusByCode(code);
    }

    @Override
    public EmpStatus getResult(CallableStatement callableStatement, int i) throws SQLException {
        //根據(jù)從數(shù)據(jù)庫中拿到的狀態(tài)碼返回枚舉對象
        int code = callableStatement.getInt(i);
        return EmpStatus.getEmpStatusByCode(code);
    }
}

實現(xiàn)TypeHandler后的方法中雾家,setParameter()定義了數(shù)據(jù)如何保存在數(shù)據(jù)庫中,直接使用PreparedStatement設置參數(shù)的方法將我們想保存的數(shù)據(jù)添加為參數(shù)绍豁,對于剩下的三個方法定義從數(shù)據(jù)庫拿到數(shù)據(jù)后如何處理芯咧,我們將數(shù)據(jù)庫中保存的狀態(tài)碼拿出來后,調(diào)用我們編寫的EmpStatus.getEmpStatusByCode(code)方法返回該枚舉對象的信息。

2敬飒、在全局配置文件中注冊該TypeHandler

語法格式如下:

<typeHandlers>
  <!-- 配置自定義的類型處理器 -->
  <typeHandler handler="自定義的TypeHandler全類名" javaType="指定要處理的類"/>
</typeHandlers>

在此處我們的配置如下:

    <typeHandlers>
        <!-- 配置自定義的類型處理器 -->
        <typeHandler handler="com.cerr.mybatis.dao.MyEnumEmpStatusTypeHandler" javaType="com.cerr.mybatis.EmpStatus"/>
    </typeHandlers>

當然了我們除了在全局配置文件中使用javaType來指定哪個類被我們自定義的類型處理器處理之外邪铲,我們還可以在sql映射文件中配置:

  • 對于<insert>標簽,我們直接在傳參時加上typeHandler屬性即可无拗,例如#{empStatus,typeHandler=com.cerr.mybatis.dao.MyEnumEmpStatusTypeHandler}带到。
  • 對于<select>標簽,我們可以在定義<resultMap>的時候英染,對于枚舉屬性揽惹,加上一個typeHandler屬性并指定為我們自定義的類型處理器即可。
  • 但是税迷,如果使用這種方法指定使用類型處理器的話永丝,比如保證<insert><select>標簽使用的是同一個類型處理器。
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末箭养,一起剝皮案震驚了整個濱河市慕嚷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌毕泌,老刑警劉巖喝检,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異撼泛,居然都是意外死亡挠说,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門愿题,熙熙樓的掌柜王于貴愁眉苦臉地迎上來损俭,“玉大人,你說我怎么就攤上這事潘酗「吮” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵仔夺,是天一觀的道長琐脏。 經(jīng)常有香客問我,道長缸兔,這世上最難降的妖魔是什么日裙? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮惰蜜,結(jié)果婚禮上昂拂,老公的妹妹穿的比我還像新娘。我一直安慰自己抛猖,他們只是感情好政钟,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布路克。 她就那樣靜靜地躺著,像睡著了一般养交。 火紅的嫁衣襯著肌膚如雪精算。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天碎连,我揣著相機與錄音灰羽,去河邊找鬼。 笑死鱼辙,一個胖子當著我的面吹牛廉嚼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倒戏,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼怠噪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了杜跷?” 一聲冷哼從身側(cè)響起傍念,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎葛闷,沒想到半個月后憋槐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡淑趾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年阳仔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扣泊。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡近范,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出延蟹,到底是詐尸還是另有隱情评矩,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布等孵,位于F島的核電站,受9級特大地震影響蹂空,放射性物質(zhì)發(fā)生泄漏俯萌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一上枕、第九天 我趴在偏房一處隱蔽的房頂上張望咐熙。 院中可真熱鬧,春花似錦辨萍、人聲如沸棋恼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爪飘。三九已至义起,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間师崎,已是汗流浹背默终。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留犁罩,地道東北人齐蔽。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像床估,于是被迫代替她去往敵國和親含滴。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

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