一 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攔截器的使用講清楚還是比較難的,因為里面設計的到的東西太多了,用代碼才是最好說話的,所以我在實例里面都盡可能的把注解寫的很詳細。希望能對大家有點幫助。