更新時間:2022-03-23 10:38:05 來源:動力節點 瀏覽3029次
基于業務來看,想要按月分表,因此數據庫表里增加了一個string類型字段 account_month 來記錄月份,分表字段就使用account_month。
分表表名:表名_年月 例如明細表:ebs_date_detail_201607。
分表是一月一張表,分表的建立就是默認建立了12個分表,如果超出了,后續再手工添加吧。也可以寫個腳本每月底創建下一個月的表,但是覺得沒啥必要。就算哪天忘記添加了,代碼邏輯的異常處理流程里面也能夠保證我的數據不丟失,啟動一下異常數據處理也就妥妥的了。
在sql語言里面會要求帶上分表字段,通過分表字段計算得到分表的表名,然后替換掉原來的sql,直接將數據路由到指定的分表就行了。
聽起來好像很簡單的樣子,那么就這么出發吧。
分表開始之前的問題:
Mybatis如何找到我們新增的攔截服務。
自定義的攔截服務應該在什么時間攔截查詢動作。即什么時間截斷Mybatis執行流。
自定義的攔截服務應該攔截什么樣的對象。不能攔截什么樣的對象。
自定義的攔截服務攔截的對象應該具有什么動作才能被攔截。
自定義的攔截服務如何獲取上下文中傳入的參數信息。
如何把簡單查詢,神不知鬼不覺的,無侵入性的替換為分表查詢語句。
最后,攔截器應該如何交還被截斷的Mybatis執行流。
帶著這些問題,我們來看看我們自定義的攔截服務是如何實現的。
(1)Mybatis如何找到我們新增的攔截服務
對于攔截器Mybatis為我們提供了一個Interceptor接口,前面有提到,通過實現該接口就可以定義我們自己的攔截器。自定義的攔截器需要交給Mybatis管理,這樣才能使得Mybatis的執行與攔截器的執行結合在一起,即,攔截器需要注冊到mybatis-config配置文件中。
通過在Mybatis配置文件中plugins元素下的plugin元素來進行。一個plugin對應著一個攔截器,在plugin元素下面我們可以指定若干個property子元素。Mybatis在注冊定義的攔截器時會先把對應攔截器下面的所有property通過Interceptor的setProperties方法注入給對應的攔截器。
配置文件:mybatis-config.xml
<configuration>
<plugins>
<plugin interceptor="com.selicoco.sango.common.database.paginator.interceptor.ShardTableInterceptor">
</plugin>
</plugins>
</configuration>
(2)什么時間截斷Mybatis執行流
Mybatis允許我們能夠進行切入的點:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
因為我是想要通過替換原來SQL中的表名來實現分表,包括查詢,新增,刪除等操作,所以攔截的合理時機選在StatementHandler中prepare。
執行流在PreparedStatementHandler.instantiateStatement()方法中 return connection.prepareStatement(sql); 最終真正的執行了語句。
所以攔截器的注解內容:
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
(3)應該攔截什么樣的對象
并不是所有的表都進行了分表,也不是所有的表都需要攔截處理。所以我們要根據某些配置來確定哪些需要被處理。
這里主要使用注解的方式,設置了對應的參數。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TableSeg {
//表名
public String tableName();
// 分表方式,取模,如%5:表示取5余數,
// 按時間,如MONTH:表示按月分表
// 如果不設置,直接根據shardBy值分表
public String shardType();
//根據什么字段分表 ,多個字段用數學表達表示,如a+b a-b
public String shardBy();
// 根據什么字段分表,多個字段用數學表達表示,如a+b a-b
public String shardByTable();
}
注解完成后,在mapper上去配置。如果是自定義的查詢語句和返回,沒有對應的mapper文件,那么在對應的dao 上進行配置就可以了。
@TableSeg(tableName="ebs_date_detail",shardType="MONTH",shardBy="accountMonth",shardByTable="account_month")
public interface EbsDataDetailMapper {}
@Repository
@TableSeg(tableName="ebs_date_detail",shardType="MONTH",shardBy="accountMonth",shardByTable="account_month")
public class EbsDataDetailDao {}
(4)如何獲取上下文中傳入的參數
首先,如何拿到執行前已經組裝好的語句。分兩種情況來說,查詢和更新。
不說話先看圖:
新增數據的時候,我們從boundSql里面的additionalParameters 里面能輕松拿到注解上面 shardBy="accountMonth"所對應的參數值。然后根據參數來生成分表語句,一切順利。
如此簡單,覺得自己好機智。開心的去碼后面的代碼了,等到單測的時候執行查詢,然后就報錯啦。只能Debug看看。
沒有想到,都是mybatis的動態sql,結果參數方式竟然不同,想來也只能自己去取參數了。參數在哪里?看圖
具體的就看后面實現代碼吧,反正就是通過兩種方式取到我們要的分表字段的參數值,這樣才能求得分表表名。
(5)真正實現分表查詢語句
攔截器主要的作用是讀取配置,根據配置的切分策略和字段,來切分表,然后替換原執行的SQL,從而實現自動切分。
String accountMonth = genShardByValue(metaStatementHandler, mappedStatement ,tableSeg, boundSql);
String newSql = boundSql.getSql().replace(tableSeg.tableName(), tableSeg.tableName() + "_" + accountMonth);
if (newSql != null) {
logger.debug(tag, "分表后SQL =====>" + newSql);
metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
}
(6)交還被截斷的Mybatis執行流
把原有的簡單查詢語句替換為分表查詢語句了,現在是時候將程序的控制權交還給Mybatis了
// 傳遞給下一個攔截器處理
return invocation.proceed();
(1)配置文件
見本文: 3.1 Mybatis如何找到我們新增的攔截服務 -- mybatis-config.xml
(2)分表配置注解
分表注解定義、mapper注解配置、DAO注解配置
見本文: 3.3 應該攔截什么樣的對象
(3)分表實現
分表具體實現
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class ShardTableInterceptor implements Interceptor {
private final static Logger logger = LoggerFactory.getLogger(ShardTableInterceptor.class);
private static final String tag = ShardTableInterceptor.class.getName();
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(statementHandler);
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
String sqlId = mappedStatement.getId();
String className = sqlId.substring(0, sqlId.lastIndexOf("."));
Class<?> classObj = Class.forName(className);
TableSeg tableSeg = classObj.getAnnotation(TableSeg.class);
if(null == tableSeg){
//不需要分表,直接傳遞給下一個攔截器處理
return invocation.proceed();
}?
//根據配置獲取分表字段,生成分表SQL
String accountMonth = genShardByValue(metaStatementHandler, mappedStatement ,tableSeg, boundSql);
String newSql = boundSql.getSql().replace(tableSeg.tableName(), tableSeg.tableName() + "_" + accountMonth);
if (newSql != null) {
logger.debug(tag, "分表后SQL =====>" + newSql);
metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
}
// 傳遞給下一個攔截器處理
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 當目標類是StatementHandler類型時,才包裝目標類,否者直接返回目標本身,減少目標被代理的次數
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
logger.info("scribeDbNames:" + properties.getProperty("scribeDbNames"));
}
//根據配置獲取分表的表名后綴
private String genShardByValue(MetaObject metaStatementHandler,MappedStatement mappedStatement, TableSeg tableSeg, BoundSql boundSql) {
String accountMonth = null;
Map<String, Object> additionalParameters = (Map<String, Object>) metaStatementHandler.getValue("delegate.boundSql.additionalParameters");
if (null != additionalParameters.get(tableSeg.shardBy())) {
accountMonth = boundSql.getAdditionalParameter(tableSeg.shardBy()).toString();
} else {
Configuration configuration = mappedStatement.getConfiguration();
String showSql = showSql(configuration,boundSql);
accountMonth = getShardByValue(showSql,tableSeg);
}
return accountMonth;
}
//根據配置獲取分表參數值
public static String getShardByValue(String showSql,TableSeg tableSeg) {
final String conditionWhere = "where";
String accountMonth = null ;
if(StringUtils.isBlank(showSql)){
return null;
}else{
String[] sqlSplit = showSql.toLowerCase().split(conditionWhere);
if(sqlSplit.length>1 && sqlSplit[1].contains(tableSeg.shardByTable())){
accountMonth = sqlSplit[1].replace(" ","").split(tableSeg.shardByTable())[1].substring(2,8);
}
}
return accountMonth;
}
//組裝查詢語句參數
public static String showSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
}
}
}
}else{
return null;
}
return sql;
}
private static String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
}
以上就是關于“基于MyBatis分表的實現”介紹,大家如果想了解更多相關知識,可以關注一下動力節點的Mybatis-Plus視頻教程,里面的課程內容細致全面,有更豐富的知識等著大家去學習,希望對大家能夠有所幫助哦。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習