一、業務場景
考慮到新專案中部分與業務資料相關的表在後期資料量會比較大,架構師在最開始設計專案中與業務資料相關的表時,就已經考慮使用分表來
進行處理,給業務資料相關的每張表都新增統一批次的字尾,查詢這些資料時,根據不同表名的字尾和來查詢對應的資料資訊。如果能夠動態的
更改資料表,比如將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的攔截器之後,也明白他是如何處理的啦。只需要在上面的語句中做簡單的處理即可實現,非常方便。