更新時間:2022-03-23 10:38:05 來源:動力節(jié)點(diǎn) 瀏覽3043次
基于業(yè)務(wù)來看,想要按月分表,因此數(shù)據(jù)庫表里增加了一個string類型字段 account_month 來記錄月份,分表字段就使用account_month。
分表表名:表名_年月 例如明細(xì)表:ebs_date_detail_201607。
分表是一月一張表,分表的建立就是默認(rèn)建立了12個分表,如果超出了,后續(xù)再手工添加吧。也可以寫個腳本每月底創(chuàng)建下一個月的表,但是覺得沒啥必要。就算哪天忘記添加了,代碼邏輯的異常處理流程里面也能夠保證我的數(shù)據(jù)不丟失,啟動一下異常數(shù)據(jù)處理也就妥妥的了。
在sql語言里面會要求帶上分表字段,通過分表字段計(jì)算得到分表的表名,然后替換掉原來的sql,直接將數(shù)據(jù)路由到指定的分表就行了。
聽起來好像很簡單的樣子,那么就這么出發(fā)吧。
分表開始之前的問題:
Mybatis如何找到我們新增的攔截服務(wù)。
自定義的攔截服務(wù)應(yīng)該在什么時間攔截查詢動作。即什么時間截斷Mybatis執(zhí)行流。
自定義的攔截服務(wù)應(yīng)該攔截什么樣的對象。不能攔截什么樣的對象。
自定義的攔截服務(wù)攔截的對象應(yīng)該具有什么動作才能被攔截。
自定義的攔截服務(wù)如何獲取上下文中傳入的參數(shù)信息。
如何把簡單查詢,神不知鬼不覺的,無侵入性的替換為分表查詢語句。
最后,攔截器應(yīng)該如何交還被截斷的Mybatis執(zhí)行流。
帶著這些問題,我們來看看我們自定義的攔截服務(wù)是如何實(shí)現(xiàn)的。
(1)Mybatis如何找到我們新增的攔截服務(wù)
對于攔截器Mybatis為我們提供了一個Interceptor接口,前面有提到,通過實(shí)現(xiàn)該接口就可以定義我們自己的攔截器。自定義的攔截器需要交給Mybatis管理,這樣才能使得Mybatis的執(zhí)行與攔截器的執(zhí)行結(jié)合在一起,即,攔截器需要注冊到mybatis-config配置文件中。
通過在Mybatis配置文件中plugins元素下的plugin元素來進(jìn)行。一個plugin對應(yīng)著一個攔截器,在plugin元素下面我們可以指定若干個property子元素。Mybatis在注冊定義的攔截器時會先把對應(yīng)攔截器下面的所有property通過Interceptor的setProperties方法注入給對應(yīng)的攔截器。
配置文件:mybatis-config.xml
<configuration>
<plugins>
<plugin interceptor="com.selicoco.sango.common.database.paginator.interceptor.ShardTableInterceptor">
</plugin>
</plugins>
</configuration>
(2)什么時間截斷Mybatis執(zhí)行流
Mybatis允許我們能夠進(jìn)行切入的點(diǎn):
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
因?yàn)槲沂窍胍ㄟ^替換原來SQL中的表名來實(shí)現(xiàn)分表,包括查詢,新增,刪除等操作,所以攔截的合理時機(jī)選在StatementHandler中prepare。
執(zhí)行流在PreparedStatementHandler.instantiateStatement()方法中 return connection.prepareStatement(sql); 最終真正的執(zhí)行了語句。
所以攔截器的注解內(nèi)容:
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
(3)應(yīng)該攔截什么樣的對象
并不是所有的表都進(jìn)行了分表,也不是所有的表都需要攔截處理。所以我們要根據(jù)某些配置來確定哪些需要被處理。
這里主要使用注解的方式,設(shè)置了對應(yīng)的參數(shù)。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TableSeg {
//表名
public String tableName();
// 分表方式,取模,如%5:表示取5余數(shù),
// 按時間,如MONTH:表示按月分表
// 如果不設(shè)置,直接根據(jù)shardBy值分表
public String shardType();
//根據(jù)什么字段分表 ,多個字段用數(shù)學(xué)表達(dá)表示,如a+b a-b
public String shardBy();
// 根據(jù)什么字段分表,多個字段用數(shù)學(xué)表達(dá)表示,如a+b a-b
public String shardByTable();
}
注解完成后,在mapper上去配置。如果是自定義的查詢語句和返回,沒有對應(yīng)的mapper文件,那么在對應(yīng)的dao 上進(jìn)行配置就可以了。
@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)如何獲取上下文中傳入的參數(shù)
首先,如何拿到執(zhí)行前已經(jīng)組裝好的語句。分兩種情況來說,查詢和更新。
不說話先看圖:
新增數(shù)據(jù)的時候,我們從boundSql里面的additionalParameters 里面能輕松拿到注解上面 shardBy="accountMonth"所對應(yīng)的參數(shù)值。然后根據(jù)參數(shù)來生成分表語句,一切順利。
如此簡單,覺得自己好機(jī)智。開心的去碼后面的代碼了,等到單測的時候執(zhí)行查詢,然后就報錯啦。只能Debug看看。
沒有想到,都是mybatis的動態(tài)sql,結(jié)果參數(shù)方式竟然不同,想來也只能自己去取參數(shù)了。參數(shù)在哪里?看圖
具體的就看后面實(shí)現(xiàn)代碼吧,反正就是通過兩種方式取到我們要的分表字段的參數(shù)值,這樣才能求得分表表名。
(5)真正實(shí)現(xiàn)分表查詢語句
攔截器主要的作用是讀取配置,根據(jù)配置的切分策略和字段,來切分表,然后替換原執(zhí)行的SQL,從而實(shí)現(xiàn)自動切分。
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執(zhí)行流
把原有的簡單查詢語句替換為分表查詢語句了,現(xiàn)在是時候?qū)⒊绦虻目刂茩?quán)交還給Mybatis了
// 傳遞給下一個攔截器處理
return invocation.proceed();
(1)配置文件
見本文: 3.1 Mybatis如何找到我們新增的攔截服務(wù) -- mybatis-config.xml
(2)分表配置注解
分表注解定義、mapper注解配置、DAO注解配置
見本文: 3.3 應(yīng)該攔截什么樣的對象
(3)分表實(shí)現(xiàn)
分表具體實(shí)現(xiàn)
@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();
}?
//根據(jù)配置獲取分表字段,生成分表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) {
// 當(dāng)目標(biāo)類是StatementHandler類型時,才包裝目標(biāo)類,否者直接返回目標(biāo)本身,減少目標(biāo)被代理的次數(shù)
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
logger.info("scribeDbNames:" + properties.getProperty("scribeDbNames"));
}
//根據(jù)配置獲取分表的表名后綴
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;
}
//根據(jù)配置獲取分表參數(shù)值
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;
}
//組裝查詢語句參數(shù)
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;
}
}
以上就是關(guān)于“基于MyBatis分表的實(shí)現(xiàn)”介紹,大家如果想了解更多相關(guān)知識,可以關(guān)注一下動力節(jié)點(diǎn)的Mybatis-Plus視頻教程,里面的課程內(nèi)容細(xì)致全面,有更豐富的知識等著大家去學(xué)習(xí),希望對大家能夠有所幫助哦。
初級 202925
初級 203221
初級 202629
初級 203743