一 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)幫助及塘。