mybatis-攔截器實際應用-替換表名-2022新專案

2022-07-16 06:01:32

一、業務場景

  考慮到新專案中部分與業務資料相關的表在後期資料量會比較大,架構師在最開始設計專案中與業務資料相關的表時,就已經考慮使用分表來

進行處理,給業務資料相關的每張表都新增統一批次的字尾,查詢這些資料時,根據不同表名的字尾和來查詢對應的資料資訊。如果能夠動態的

更改資料表,比如將ai_user表更改為ai_user_20220001,這樣就可以動態的查詢不同表中的資料。

 

二、需求分析

  最開始考慮使用的是在xml檔案中使用if來做條件判斷,根據傳入引數的不同來動態查詢不同的表。這種方式最開始的時候也沒什麼問題,只是

當需要查詢的表非常多的時候,需要寫的判斷語句也會同樣的增多,如果需要改動,則非常不便於進行統一處理。就好比是鑑權,如果在每個請求

的方法中都去寫一段鑑權的程式碼,如果需要進行改動這段程式碼,那改起來頭都大啦。寫了幾個xml檔案後,自己就在考慮能不能像java程式碼一樣,寫

一個攔截器之類的,進行統一處理呢?

三、解決方案

  先確定一個大方向,方向確定後就開始去找解決方案,去網上搜尋相關的內容,學習資料,檢視各種博文等等,需要快速學習,並且實際使用。

這個攔截器需要能夠獲取到之前的舊有的執行SQL語句,還需要能夠獲取到執行語句傳入的引數,然後根據傳入的引數來動態的修改表名,讓其生成

新的SQL語句,最後讓攔截器執行新的SQL語句即可。這種方式有些類似於PageHelper外掛的處理方式,如果改外掛檢測到需要分頁查詢的SQL語句,

在就是使用攔截器進行處理,更改需要查詢的SQL語句,最終完成分頁的查詢。

  然後開始不斷地探索、嘗試,同事最後找到適合的解決方案。範例程式碼如下,由於是內網開發拿不到原始碼,只寫了範例程式碼來進行講解說明.

package mybatis.interceptor;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.util.Map;
import java.util.Properties;

/**
* @Author yilang
* @Description TODO
* @Date 2022/7/7 10:29
* @Version 1.0
*/

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

private final static String SOURCE_TABLE = "app_gift_info";

@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = MetaObject.forObject(statementHandler, new DefaultObjectFactory(),
new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
BoundSql boundSql = (BoundSql)metaObject.getValue("delegate.boundSql");
// 獲取執行的SQL
String sql = boundSql.getSql();
// 獲取執行的SQL引數
//MapperMethod.ParamMap raramMap = (MapperMethod.ParamMap) boundSql.getParameterObject();
//Object param = raramMap.get("param");
Object parameterObject = boundSql.getParameterObject();
String s = JSON.toJSONString(parameterObject);
Map map = JSONObject.parseObject(s, Map.class);

// 替換表名
//String oldTable = (String)map.get("newTable");
Object tempTable = map.get("newTablea");
if (tempTable == null){
tempTable = "";
}
String newTable = (String)tempTable;
if (sql.contains(SOURCE_TABLE)) {
sql = sql.replaceAll(SOURCE_TABLE, newTable);
}
// 替換執行的的SQL.
metaObject.setValue("delegate.boundSql.sql", sql);

return invocation.proceed();
}

@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}

@Override
public void setProperties(Properties properties) {
}
}

說明: @Component 註解:不用多說,用來將當前的自定義Mybatis攔截器註冊到Spring容器中,讓其進行統一管理。

 @Intercepts註解:其value為Signature類數值,註解在Interceptor實現類上,表示實現類對哪些sql執行類(實現Executor)的哪些方法切入

@Signature:表示一個唯一的Interceptor實現類的一個方法,以及入參.可參考這篇文章 https://zhuanlan.zhihu.com/p/286476884

 

經過反反覆覆地偵錯,最終實現自己想要的功能,能夠獲取到執行的SQL和傳入的引數,然後替換SQL中的表,最後執行新的SQL語句。

最後測試發現完全可行。

參考文章-

https://blog.csdn.net/u011625492/article/details/78426628

https://zhuanlan.zhihu.com/p/345438831

https://www.cnblogs.com/blueSkyline/p/10178992.html

 

測試過程中遇到的問題:在獲取SQL執行引數時,如果某個執行方法中只有一個引數,並且沒有新增@Param註解時,則轉換為map集合會

報類轉換異常的錯誤,這個需要注意。其他引數則可以根據自己的需要進行更改。

 

拓展:以前的專案中有一個需求,專案需要記錄所有執行的SQL語句。這個功能是其他同事的做的,那時自己也很好奇如何完成的,如何實現

的,現在使用了Mybatis的攔截器之後,也明白他是如何處理的啦。只需要在上面的語句中做簡單的處理即可實現,非常方便。