Mybatis攔截器實現(xiàn)數(shù)據(jù)分表
在項目中我們是用Mybatis + TKMapper + MYSQL存儲了一些消息日志,但是現(xiàn)在隨著業(yè)務(wù)數(shù)據(jù)暴增, 單表支撐不了這么多數(shù)據(jù). 因此決定把表做水平切分, 按照月份來給表進行切分。這樣當(dāng)我們需要housekeep數(shù)據(jù)的時候,就可以直接drop掉表了,不論是備份還是刪除效率都會比較高
那我們就會是用到Mybaits的攔截器
Mybatis插件機制:
Mybatis支持插件(plugin), 講得通俗一點就是攔截器(interceptor). > 它支持ParameterHandler/StatementHandler/Executor/ResultSetHandler這四個級> 別進行攔截.
總體概況為:
- 攔截參數(shù)的處理(ParameterHandler)
- 攔截Sql語法構(gòu)建的處理(StatementHandler)
- 攔截執(zhí)行器的方法(Executor)
- 攔截結(jié)果集的處理(ResultSetHandler)
分表注解
首先我們想要的效果是只有指定的表才需要分表,并不是全部表都需要分表,因為攔截器是全局的,所以我們需要做特殊處理,當(dāng)只有指定的表我們才進行攔截和處理。
第二點,我們的分表策略目前是按照年月份來進行分表,以后可能會按照天來分表,也就是分表策略是可變的,所以我們的代碼是可擴展的,能夠自定義分表策略的。
基于以上兩點,我們會構(gòu)造一個注解,作用于表上,為我們指定分表策略,以及標(biāo)記當(dāng)前表是需要做分表的。
@Target({TYPE}) @Retention(RUNTIME) public @interface TableShard { Class<? extends TableShardStrategy> shardStrategy(); }
分表策略接口
public interface TableShardStrategy { String getTableShardName(String tableName); }
從方法名就可以看出來,返回的是一個分表后的表名
目前只有一個實現(xiàn)類,也就是按照年月份分表
public class DateTableShardStrategy implements TableShardStrategy { public static final String PATTERN = "yyyyMM"; @Override public String getTableShardName(String tableName) { YearMonth yearMonth = Optional.ofNullable(ProcessContextHolder.get()).orElse(ProcessInfo.builder().yearMonth(TimeUtils.getYearMonth()).build()).getYearMonth(); String date = DateTimeFormatter.ofPattern(PATTERN).format(yearMonth); return tableName + "_" + date; } }
具體的實現(xiàn)其實就是根據(jù)當(dāng)前實現(xiàn)獲取月份,然后在當(dāng)前的表名后面加上年月。 至于為什么中間從ProcessContextHolder.get()
獲取時間呢,其實是當(dāng)有消息進來的時候,我們會在ThreadLocal存一個當(dāng)前時間作為當(dāng)前消息的全局時間。為什么需要這么做呢,就是擔(dān)心一條消息在處理的過程中,出現(xiàn)跨月的情況,所以導(dǎo)致同一條消息的數(shù)據(jù),存儲在了兩個不同的表。所以需要維護一個全局的時間。
之后我們就可以在表的實體類上加上該注解了@TableShard(shardStrategy = DateTableShardStrategy.class)
Mybaits的攔截器實現(xiàn)
最后就是Mybatis的攔截器實現(xiàn)了。
@Intercepts({ @Signature( type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class} ) }) @Slf4j @Component @ConditionalOnProperty(value = "table.shard.enabled", havingValue = "true") //加上了table.shard.enabled 該配置才會生效 public class MybatisStatementInterceptor implements Interceptor { private static final ReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory(); public static final String DELEGATE_BOUND_SQL_SQL = "delegate.boundSql.sql"; public static final String DELEGATE_MAPPED_STATEMENT = "delegate.mappedStatement"; @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, defaultReflectorFactory ); MappedStatement mappedStatement = (MappedStatement) metaObject.getValue(DELEGATE_MAPPED_STATEMENT); Class<?> clazz = getTableClass(mappedStatement); if (clazz == null) { return invocation.proceed(); } TableShard tableShard = clazz.getAnnotation(TableShard.class); //獲取表實體類上的注解 Table table = clazz.getAnnotation(Table.class); if (tableShard != null && table != null) { //如果注解存在就執(zhí)行分表策略 String tableName = table.name(); Class<? extends TableShardStrategy> strategyClazz = tableShard.shardStrategy(); TableShardStrategy strategy = strategyClazz.getDeclaredConstructor().newInstance(); String tableShardName = strategy.getTableShardName(tableName); String sql = (String) metaObject.getValue(DELEGATE_BOUND_SQL_SQL); metaObject.setValue(DELEGATE_BOUND_SQL_SQL, sql.replaceAll(tableName, tableShardName.toUpperCase())); //替換表名 } return invocation.proceed(); } private Class<?> getTableClass(MappedStatement mappedStatement) throws ClassNotFoundException { String className = mappedStatement.getId(); className = className.substring(0, className.lastIndexOf('.')); //獲取到BaseMapper的實現(xiàn)類 Class<?> clazz = Class.forName(className); if (BaseMapper.class.isAssignableFrom(clazz)) { return (Class<?>) ((ParameterizedType) (clazz.getGenericInterfaces()[0])).getActualTypeArguments()[0]; //獲取表實體類 //public interface XXXMapper extends BaseMapper<XXX> 其實就是獲取到泛型中的具體表實體類 return null; } @Override public Object plugin(Object target) { if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } else { return target; } } }
好了,這樣就實現(xiàn)了使用Mybatis攔截器進行數(shù)據(jù)分表
參考
通過MyBatis攔截器實現(xiàn)增刪改查參數(shù)的加/解密(已上線項目)
mybatis自定義攔截器攔截sql,處理createTime,updateTime,createBy,updateBy等問題
mybatis攔截器實現(xiàn)數(shù)據(jù)庫表水平切分
到此這篇關(guān)于Mybatis攔截器實現(xiàn)數(shù)據(jù)分表的文章就介紹到這了,更多相關(guān)Mybatis攔截器分表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實現(xiàn)ZooKeeper的zNode監(jiān)控
這篇文章主要介紹了Java實現(xiàn)ZooKeeper的zNode監(jiān)控問題,本文給大家介紹的非常詳細,具有一定的參考借鑒價值 ,需要的朋友可以參考下2019-08-08Java線性結(jié)構(gòu)中棧、隊列和串的基本概念和特點詳解
前幾天小編給大家介紹了Java線性結(jié)構(gòu)中的鏈表,除了鏈表這種結(jié)構(gòu)之外,實際上還有棧、隊列、串等結(jié)構(gòu),那么這些結(jié)構(gòu)又有哪些特點呢,本文就給大家詳細的介紹一下,感興趣的小伙伴跟著小編一起來看看吧2023-07-07文件路徑正確,報java.io.FileNotFoundException異常的原因及解決辦法
這篇文章主要介紹了文件路徑正確,報java.io.FileNotFoundException異常的原因及解決辦法的相關(guān)資料,需要的朋友可以參考下2016-04-04詳解Java利用同步塊synchronized()保證并發(fā)安全
這篇文章主要介紹了Java利用同步塊synchronized()保證并發(fā)安全,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03