Mybatis攔截器

一 Mybatis攔截器介紹

? ? ? ?Mybatis攔截器設(shè)計(jì)的初衷就是為了供用戶在某些時(shí)候可以實(shí)現(xiàn)自己的邏輯而不必去動(dòng)Mybatis固有的邏輯。通過Mybatis攔截器我們可以攔截某些方法的調(diào)用,我們可以選擇在這些被攔截的方法執(zhí)行前后加上某些邏輯窒百,也可以在執(zhí)行這些被攔截的方法時(shí)執(zhí)行自己的邏輯而不再執(zhí)行被攔截的方法疏魏。所以Mybatis攔截器的使用范圍是非常廣泛的。

? ? ? ?Mybatis里面的核心對(duì)象還是比較多,如下:

Mybatis核心對(duì)象 解釋
SqlSession 作為MyBatis工作的主要頂層API们陆,表示和數(shù)據(jù)庫交互的會(huì)話寒瓦,完成必要數(shù)據(jù)庫增刪改查功能
Executor MyBatis執(zhí)行器,是MyBatis 調(diào)度的核心坪仇,負(fù)責(zé)SQL語句的生成和查詢緩存的維護(hù)
StatementHandler 封裝了JDBC Statement操作杂腰,負(fù)責(zé)對(duì)JDBC statement 的操作,如設(shè)置參數(shù)椅文、將Statement結(jié)果集轉(zhuǎn)換成List集合
ParameterHandler 負(fù)責(zé)對(duì)用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement 所需要的參數(shù)
ResultSetHandler 負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集對(duì)象轉(zhuǎn)換成List類型的集合喂很;
TypeHandler 負(fù)責(zé)java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的映射和轉(zhuǎn)換
MappedStatement MappedStatement維護(hù)了一條mapper.xml文件里面 select 惜颇、update、delete少辣、insert節(jié)點(diǎn)的封裝
SqlSource 負(fù)責(zé)根據(jù)用戶傳遞的parameterObject凌摄,動(dòng)態(tài)地生成SQL語句,將信息封裝到BoundSql對(duì)象中漓帅,并返回
BoundSql 表示動(dòng)態(tài)生成的SQL語句以及相應(yīng)的參數(shù)信息
Configuration MyBatis所有的配置信息都維持在Configuration對(duì)象之中

? ? ? ?Mybatis攔截器并不是每個(gè)對(duì)象里面的方法都可以被攔截的锨亏。Mybatis攔截器只能攔截Executor、ParameterHandler忙干、StatementHandler器予、ResultSetHandler四個(gè)對(duì)象里面的方法。

  • Executor

? ? ? ?Mybatis中所有的Mapper語句的執(zhí)行都是通過Executor進(jìn)行的捐迫。Executor是Mybatis的核心接口乾翔。從其定義的接口方法我們可以看出,對(duì)應(yīng)的增刪改語句是通過Executor接口的update方法進(jìn)行的施戴,查詢是通過query方法進(jìn)行的末融。Executor里面常用攔截方法如下所示。

public interface Executor {

   ...

    /**
     * 執(zhí)行update/insert/delete
     */
    int update(MappedStatement ms, Object parameter) throws SQLException;

    /**
     * 執(zhí)行查詢,先在緩存里面查找
     */
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

    /**
     * 執(zhí)行查詢
     */
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

    /**
     * 執(zhí)行查詢暇韧,查詢結(jié)果放在Cursor里面
     */
    <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

    ...


}
  • ParameterHandler

? ? ? ?ParameterHandler用來設(shè)置參數(shù)規(guī)則,當(dāng)StatementHandler使用prepare()方法后浓瞪,接下來就是使用它來設(shè)置參數(shù)懈玻。所以如果有對(duì)參數(shù)做自定義邏輯處理的時(shí)候,可以通過攔截ParameterHandler來實(shí)現(xiàn)乾颁。ParameterHandler里面可以攔截的方法解釋如下:

public interface ParameterHandler {

 ...

 /**
  * 設(shè)置參數(shù)規(guī)則的時(shí)候調(diào)用 -- PreparedStatement
  */
 void setParameters(PreparedStatement ps) throws SQLException;

 ...


}
  • StatementHandler

? ? ? ?StatementHandler負(fù)責(zé)處理Mybatis與JDBC之間Statement的交互涂乌。

public interface StatementHandler {

    ...

    /**
     * 從連接中獲取一個(gè)Statement
     */
    Statement prepare(Connection connection, Integer transactionTimeout)
            throws SQLException;

    /**
     * 設(shè)置statement執(zhí)行里所需的參數(shù)
     */
    void parameterize(Statement statement)
            throws SQLException;

    /**
     * 批量
     */
    void batch(Statement statement)
            throws SQLException;

    /**
     * 更新:update/insert/delete語句
     */
    int update(Statement statement)
            throws SQLException;

    /**
     * 執(zhí)行查詢
     */
    <E> List<E> query(Statement statement, ResultHandler resultHandler)
            throws SQLException;

    <E> Cursor<E> queryCursor(Statement statement)
            throws SQLException;

    ...

}

一般只攔截StatementHandler里面的prepare方法。

? ? ? ?在Mybatis里面RoutingStatementHandler是SimpleStatementHandler(對(duì)應(yīng)Statement)英岭、PreparedStatementHandler(對(duì)應(yīng)PreparedStatement)湾盒、CallableStatementHandler(對(duì)應(yīng)CallableStatement)的路由類,所有需要攔截StatementHandler里面的方法的時(shí)候诅妹,對(duì)RoutingStatementHandler做攔截處理就可以了罚勾,如下的寫法可以過濾掉一些不必要的攔截類。

@Intercepts({
        @Signature(
                type = StatementHandler.class,
                method = "prepare",
                args = {Connection.class, Integer.class}
        )
})
public class TableShardInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (invocation.getTarget() instanceof RoutingStatementHandler) {
            // TODO: 做自己的邏輯
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 當(dāng)目標(biāo)類是StatementHandler類型時(shí)吭狡,才包裝目標(biāo)類尖殃,否者直接返回目標(biāo)本身,減少目標(biāo)被代理的次數(shù)
        return (target instanceof RoutingStatementHandler) ? Plugin.wrap(target, this) : target;
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

關(guān)于Statement、PreparedStatement和CallableStatement的一些區(qū)別划煮。以及Statement和PreparedStatement相比PreparedStatement的優(yōu)勢(shì)在哪里送丰。強(qiáng)烈建議大家去百度下。

  • ResultSetHandler

? ? ? ?ResultSetHandler用于對(duì)查詢到的結(jié)果做處理弛秋。所以如果你有需求需要對(duì)返回結(jié)果做特殊處理的情況下可以去攔截ResultSetHandler的處理器躏。ResultSetHandler里面常用攔截方法如下:

public interface ResultSetHandler {

    /**
     * 將Statement執(zhí)行后產(chǎn)生的結(jié)果集(可能有多個(gè)結(jié)果集)映射為結(jié)果列表
     */
    <E> List<E> handleResultSets(Statement stmt) throws SQLException;
    <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

    /**
     * 處理存儲(chǔ)過程執(zhí)行后的輸出參數(shù)
     */
    void handleOutputParameters(CallableStatement cs) throws SQLException;

}

二 Mybatis攔截器的使用

? ? ? ?Mybatis攔截器的使用俐载,分兩步:自定義攔截器類、注冊(cè)攔截器類登失。

2.1 自定義攔截器類

? ? ? ?自定義的攔截器需要實(shí)現(xiàn)Interceptor接口遏佣,并且需要在自定義攔截器類上添加@Intercepts注解。

2.1.1 Interceptor接口

? ? ? ?Interceptor接口里面就三個(gè)方法壁畸。如下所示:

public interface Interceptor {

    /**
     * 代理對(duì)象每次調(diào)用的方法贼急,就是要進(jìn)行攔截的時(shí)候要執(zhí)行的方法。在這個(gè)方法里面做我們自定義的邏輯處理
     */
    Object intercept(Invocation invocation) throws Throwable;

    /**
     * plugin方法是攔截器用于封裝目標(biāo)對(duì)象的捏萍,通過該方法我們可以返回目標(biāo)對(duì)象本身太抓,也可以返回一個(gè)它的代理
     *
     * 當(dāng)返回的是代理的時(shí)候我們可以對(duì)其中的方法進(jìn)行攔截來調(diào)用intercept方法 -- Plugin.wrap(target, this)
     * 當(dāng)返回的是當(dāng)前對(duì)象的時(shí)候 就不會(huì)調(diào)用intercept方法,相當(dāng)于當(dāng)前攔截器無效
     */
    Object plugin(Object target);

    /**
     * 用于在Mybatis配置文件中指定一些屬性的令杈,注冊(cè)當(dāng)前攔截器的時(shí)候可以設(shè)置一些屬性
     */
    void setProperties(Properties properties);

}

2.1.2 @Intercepts注解

? ? ? ?Intercepts注解需要一個(gè)Signature(攔截點(diǎn))參數(shù)數(shù)組走敌。通過Signature來指定攔截哪個(gè)對(duì)象里面的哪個(gè)方法。@Intercepts注解定義如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
    /**
     * 定義攔截點(diǎn)
     * 只有符合攔截點(diǎn)的條件才會(huì)進(jìn)入到攔截器
     */
    Signature[] value();
}

? ? ? ? Signature來指定咱們需要攔截那個(gè)類對(duì)象的哪個(gè)方法逗噩。定義如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
    /**
     * 定義攔截的類 Executor掉丽、ParameterHandler、StatementHandler异雁、ResultSetHandler當(dāng)中的一個(gè)
     */
    Class<?> type();

    /**
     * 在定義攔截類的基礎(chǔ)之上捶障,在定義攔截的方法
     */
    String method();

    /**
     * 在定義攔截方法的基礎(chǔ)之上在定義攔截的方法對(duì)應(yīng)的參數(shù),
     * JAVA里面方法可能重載纲刀,不指定參數(shù)项炼,不曉得是那個(gè)方法
     */
    Class<?>[] args();
}

? ? ? ?我們舉一個(gè)例子來說明,比如我們自定義一個(gè)MybatisInterceptor類示绊,來攔截Executor類里面的兩個(gè)query锭部。自定義攔截類MybatisInterceptor

@Intercepts({
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        ),
        @Signature(
                type = Executor.class,
                method = "update",
                args = {MappedStatement.class, Object.class}
        )
})
public class MybatisInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        // TODO: 自定義攔截邏輯

    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this); // 返回代理類
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

2.2 注冊(cè)攔截器

? ? ? ?注冊(cè)攔截器就是去告訴Mybatis去使用我們的攔截器。注冊(cè)攔截器類非常的簡(jiǎn)單面褐,在@Configuration注解的類里面拌禾,@Bean我們自定義的攔截器類。比如我們需要注冊(cè)自定義的MybatisInterceptor攔截器展哭。

/**
 * mybatis配置
 */
@Configuration
public class MybatisConfiguration {

    /**
     * 注冊(cè)攔截器
     */
    @Bean
    public MybatisInterceptor mybatisInterceptor() {
        MybatisInterceptor interceptor = new MybatisInterceptor();
        Properties properties = new Properties();
        // 可以調(diào)用properties.setProperty方法來給攔截器設(shè)置一些自定義參數(shù)
        interceptor.setProperties(properties);
        return interceptor;
    }
    

}

三 Mybatis攔截器實(shí)例-自定義攔截器

? ? ? ?上面講了一大堆湃窍,最終的目的都是要使用上攔截器,接下來匪傍。我們通過幾個(gè)簡(jiǎn)單的自定義攔截器來加深對(duì)Mybatis攔截器的理解坝咐。實(shí)例代碼在鏈接地址:https://github.com/tuacy/microservice-framework 的 mybatis-interceptor module里面。

3.1 日志打印

? ? ? ?自定義LogInterceptor攔截器析恢,打印出我們每次sq執(zhí)行對(duì)應(yīng)sql語句墨坚。

3.2 分頁

? ? ? ?模仿pagehelper,咱們也來實(shí)現(xiàn)一個(gè)分頁的攔截器PageInterceptor,該攔截器也支持自定義count查詢泽篮。

3.3 分表

? ? ? ?自定義攔截器TableShardInterceptor實(shí)現(xiàn)水平分表的功能盗尸。

3.4 對(duì)查詢結(jié)果的某個(gè)字段加密

? ? ? ?自定義攔截器EncryptResultFieldInterceptor對(duì)查詢回來的結(jié)果中的某個(gè)字段進(jìn)行加密處理。

上面攔截器的實(shí)現(xiàn)帽撑,在github https://github.com/tuacy/microservice-framework 的 mybatis-interceptor module里面都能找到具體的實(shí)現(xiàn)泼各。


? ? ? ?發(fā)現(xiàn)想把Mybatis攔截器的使用講清楚還是比較難的,因?yàn)槔锩嬖O(shè)計(jì)的到的東西太多了亏拉,用代碼才是最好說話的扣蜻,所以我在實(shí)例里面都盡可能的把注解寫的很詳細(xì)。希望能對(duì)大家有點(diǎn)幫助及塘。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末莽使,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子笙僚,更是在濱河造成了極大的恐慌芳肌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肋层,死亡現(xiàn)場(chǎng)離奇詭異亿笤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)栋猖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門净薛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蒲拉,你說我怎么就攤上這事肃拜。” “怎么了全陨?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)衷掷。 經(jīng)常有香客問我辱姨,道長(zhǎng),這世上最難降的妖魔是什么戚嗅? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任雨涛,我火速辦了婚禮,結(jié)果婚禮上懦胞,老公的妹妹穿的比我還像新娘替久。我一直安慰自己,他們只是感情好躏尉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布蚯根。 她就那樣靜靜地躺著,像睡著了一般胀糜。 火紅的嫁衣襯著肌膚如雪颅拦。 梳的紋絲不亂的頭發(fā)上蒂誉,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音距帅,去河邊找鬼右锨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛碌秸,可吹牛的內(nèi)容都是我干的绍移。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼讥电,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蹂窖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起允趟,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤恼策,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后潮剪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涣楷,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年抗碰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狮斗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弧蝇,死狀恐怖碳褒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情看疗,我是刑警寧澤沙峻,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站两芳,受9級(jí)特大地震影響摔寨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜怖辆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一是复、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧竖螃,春花似錦淑廊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春蜀备,著一層夾襖步出監(jiān)牢的瞬間关摇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國打工碾阁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留输虱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓脂凶,卻偏偏與公主長(zhǎng)得像宪睹,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚕钦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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