Mybatis攔截器

一 Mybatis攔截器介紹

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

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

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

? ? ? ?Mybatis攔截器并不是每個對象里面的方法都可以被攔截的。Mybatis攔截器只能攔截Executor、ParameterHandler、StatementHandler、ResultSetHandler四個對象里面的方法。

  • Executor

? ? ? ?Mybatis中所有的Mapper語句的執(zhí)行都是通過Executor進行的。Executor是Mybatis的核心接口。從其定義的接口方法我們可以看出,對應的增刪改語句是通過Executor接口的update方法進行的,查詢是通過query方法進行的。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ù)規(guī)則,當StatementHandler使用prepare()方法后,接下來就是使用它來設置參數(shù)。所以如果有對參數(shù)做自定義邏輯處理的時候,可以通過攔截ParameterHandler來實現(xiàn)。ParameterHandler里面可以攔截的方法解釋如下:

public interface ParameterHandler {

 ...

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

 ...


}
  • StatementHandler

? ? ? ?StatementHandler負責處理Mybatis與JDBC之間Statement的交互。

public interface StatementHandler {

    ...

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

    /**
     * 設置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(對應Statement)、PreparedStatementHandler(對應PreparedStatement)、CallableStatementHandler(對應CallableStatement)的路由類,所有需要攔截StatementHandler里面的方法的時候,對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) {
        // 當目標類是StatementHandler類型時,才包裝目標類,否者直接返回目標本身,減少目標被代理的次數(shù)
        return (target instanceof RoutingStatementHandler) ? Plugin.wrap(target, this) : target;
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

關于Statement、PreparedStatement和CallableStatement的一些區(qū)別。以及Statement和PreparedStatement相比PreparedStatement的優(yōu)勢在哪里。強烈建議大家去百度下。

  • ResultSetHandler

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

public interface ResultSetHandler {

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

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

}

二 Mybatis攔截器的使用

? ? ? ?Mybatis攔截器的使用,分兩步:自定義攔截器類、注冊攔截器類。

2.1 自定義攔截器類

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

2.1.1 Interceptor接口

? ? ? ?Interceptor接口里面就三個方法。如下所示:

public interface Interceptor {

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

    /**
     * plugin方法是攔截器用于封裝目標對象的,通過該方法我們可以返回目標對象本身,也可以返回一個它的代理
     *
     * 當返回的是代理的時候我們可以對其中的方法進行攔截來調(diào)用intercept方法 -- Plugin.wrap(target, this)
     * 當返回的是當前對象的時候 就不會調(diào)用intercept方法,相當于當前攔截器無效
     */
    Object plugin(Object target);

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

}

2.1.2 @Intercepts注解

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

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

? ? ? ? Signature來指定咱們需要攔截那個類對象的哪個方法。定義如下:

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

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

    /**
     * 在定義攔截方法的基礎之上在定義攔截的方法對應的參數(shù),
     * JAVA里面方法可能重載,不指定參數(shù),不曉得是那個方法
     */
    Class<?>[] args();
}

? ? ? ?我們舉一個例子來說明,比如我們自定義一個MybatisInterceptor類,來攔截Executor類里面的兩個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 注冊攔截器

? ? ? ?注冊攔截器就是去告訴Mybatis去使用我們的攔截器。注冊攔截器類非常的簡單,在@Configuration注解的類里面,@Bean我們自定義的攔截器類。比如我們需要注冊自定義的MybatisInterceptor攔截器。

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

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

}

三 Mybatis攔截器實例-自定義攔截器

? ? ? ?上面講了一大堆,最終的目的都是要使用上攔截器,接下來。我們通過幾個簡單的自定義攔截器來加深對Mybatis攔截器的理解。實例代碼在鏈接地址:https://github.com/tuacy/microservice-framework 的 mybatis-interceptor module里面。

3.1 日志打印

? ? ? ?自定義LogInterceptor攔截器,打印出我們每次sq執(zhí)行對應sql語句。

3.2 分頁

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

3.3 分表

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

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

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

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


? ? ? ?發(fā)現(xiàn)想把Mybatis攔截器的使用講清楚還是比較難的,因為里面設計的到的東西太多了,用代碼才是最好說話的,所以我在實例里面都盡可能的把注解寫的很詳細。希望能對大家有點幫助。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容