亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁技術(shù)文章
文章詳情頁

Mybatis實(shí)現(xiàn)分表插件

瀏覽:3日期:2023-10-19 11:51:12
背景

事情是醬紫的,阿星的上級(jí)leader負(fù)責(zé)記錄信息的業(yè)務(wù),每日預(yù)估數(shù)據(jù)量是15萬左右,所以引入sharding-jdbc做分表。

上級(jí)leader完成業(yè)務(wù)的開發(fā)后,走了一波自測,git push后,就忙其他的事情去了。

項(xiàng)目的框架是SpringBoot+Mybaits

出問題了

阿星負(fù)責(zé)的業(yè)務(wù)也開發(fā)完了,熟練的git pull,準(zhǔn)備自測,單元測試run一下,上個(gè)廁所回來收工,就是這么自信。

Mybatis實(shí)現(xiàn)分表插件

回來后,看下控制臺(tái),人都傻了,一片紅,內(nèi)心不禁感嘆“如果這是股票基金該多好”。

出了問題就要解決,隨著排查深入,我的眉頭一皺發(fā)現(xiàn)事情并不簡單,怎么以前的一些代碼都報(bào)錯(cuò)了?

Mybatis實(shí)現(xiàn)分表插件

隨著排查深入,最后跟到了Mybatis源碼,發(fā)現(xiàn)罪魁禍?zhǔn)资莝harding-jdbc引起的,因?yàn)閿?shù)據(jù)源是sharding-jdbc的,導(dǎo)致后續(xù)執(zhí)行sql的是ShardingPreparedStatement。

這就意味著,sharding-jdbc影響項(xiàng)目的所有業(yè)務(wù)表,因?yàn)樽罱K數(shù)據(jù)庫交互都由ShardingPreparedStatement去做了,歷史的一些sql語句因?yàn)閟ql函數(shù)或者其他寫法,使得ShardingPreparedStatement無法處理而出現(xiàn)異常。

關(guān)鍵代碼如下

Mybatis實(shí)現(xiàn)分表插件

發(fā)現(xiàn)問題后,阿星馬上就反饋給leader了。

Mybatis實(shí)現(xiàn)分表插件

唉,本來還想摸魚的,看來摸魚的時(shí)間是沒了,還多了一項(xiàng)任務(wù)。

分析

竟然交給阿星來做了,就擼起袖子開干吧,先看看分表功能的需求

支持自定義分表策略 能控制影響范圍 通用性

分表會(huì)提前建立好,所以不需要考慮表不存在的問題,核心邏輯實(shí)現(xiàn),通過分表策略得到分表名,再把分表名動(dòng)態(tài)替換到sql。

Mybatis實(shí)現(xiàn)分表插件

分表策略

為了支持分表策略,我們需要先定義分表策略抽象接口,定義如下

/** * @Author 程序猿阿星 * @Description 分表策略接口 * @Date 2021/5/9 */public interface ITableShardStrategy { /** * @author: 程序猿阿星 * @description: 生成分表名 * @param tableNamePrefix 表前綴名 * @param value 值 * @date: 2021/5/9 * @return: java.lang.String */ String generateTableName(String tableNamePrefix,Object value); /** * 驗(yàn)證tableNamePrefix */ default void verificationTableNamePrefix(String tableNamePrefix){if (StrUtil.isBlank(tableNamePrefix)) { throw new RuntimeException('tableNamePrefix is null');} }}

generateTableName函數(shù)的任務(wù)就是生成分表名,入?yún)⒂衪ableNamePrefix、value,tableNamePrefix為分表前綴,value作為生成分表名的邏輯參數(shù)。

verificationTableNamePrefix函數(shù)驗(yàn)證tableNamePrefix必填,提供給實(shí)現(xiàn)類使用。

為了方便理解,下面是id取模策略代碼,取模兩張表

/** * @Author 程序猿阿星 * @Description 分表策略id * @Date 2021/5/9 */@Componentpublic class TableShardStrategyId implements ITableShardStrategy { @Override public String generateTableName(String tableNamePrefix, Object value) {verificationTableNamePrefix(tableNamePrefix);if (value == null || StrUtil.isBlank(value.toString())) { throw new RuntimeException('value is null');}long id = Long.parseLong(value.toString());//此處可以緩存優(yōu)化return tableNamePrefix + '_' + (id % 2); }}

傳入進(jìn)來的value是id值,用tableNamePrefix拼接id取模后的值,得到分表名返回。

控制影響范圍

分表策略已經(jīng)抽象出來,下面要考慮控制影響范圍,我們都知道Mybatis規(guī)范中每個(gè)Mapper類對應(yīng)一張業(yè)務(wù)主體表,Mapper類的函數(shù)對應(yīng)業(yè)務(wù)主體表的相關(guān)sql。

阿星想著,可以給Mapper類打上注解,代表該Mpaaer類對應(yīng)的業(yè)務(wù)主體表有分表需求,從規(guī)范來說Mapper類的每個(gè)函數(shù)對應(yīng)的主體表都是正確的,但是有些同學(xué)可能不會(huì)按規(guī)范來寫。

假設(shè)Mpaaer類對應(yīng)的是B表,Mpaaer類的某個(gè)函數(shù)寫著A表的sql,甚至是歷史遺留問題,所以注解不僅僅可以打在Mapper類上,同時(shí)還可以打在Mapper類的任意一個(gè)函數(shù)上,并且保證小粒度覆蓋粗粒度。

阿星這里自定義分表注解,代碼如下

/** * @Author 程序猿阿星 * @Description 分表注解 * @Date 2021/5/9 */@Target(value = {ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface TableShard { // 表前綴名 String tableNamePrefix(); //值 String value() default ''; //是否是字段名,如果是需要解析請求參數(shù)改字段名的值(默認(rèn)否) boolean fieldFlag() default false; // 對應(yīng)的分表策略類 Class<? extends ITableShardStrategy> shardStrategy();}

注解的作用范圍是類、接口、函數(shù),運(yùn)行時(shí)生效。

tableNamePrefix與shardStrategy屬性都好理解,表前綴名和分表策略,剩下的value與fieldFlag要怎么理解,分表策略分兩類,第一類依賴表中某個(gè)字段值,第二類則不依賴。

根據(jù)企業(yè)id取模,屬于第一類,此處的value設(shè)置企業(yè)id入?yún)⒆侄蚊琭ieldFlag為true,意味著,會(huì)去解析獲取企業(yè)id字段名對應(yīng)的值。

根據(jù)日期分表,屬于第二類,直接在分表策略實(shí)現(xiàn)類里面寫就行了,不依賴表字段值,value與fieldFlag無需填寫,當(dāng)然你value也可以設(shè)置時(shí)間格式,具體看分表策略實(shí)現(xiàn)類的邏輯。

通用性

抽象分表策略與分表注解都搞定了,最后一步就是根據(jù)分表注解信息,去執(zhí)行分表策略得到分表名,再把分表名動(dòng)態(tài)替換到sql中,同時(shí)具有通用性。

Mybatis框架中,有攔截器機(jī)制做擴(kuò)展,我們只需要攔截StatementHandler#prepare函數(shù),即StatementHandle創(chuàng)建Statement之前,先把sql里面的表名動(dòng)態(tài)替換成分表名。

Mybatis分表攔截器流程圖如下

Mybatis實(shí)現(xiàn)分表插件

Mybatis分表攔截器代碼如下,有點(diǎn)長哈,主流程看intercept函數(shù)就好了。

/** * @Author 程序員阿星 * @Description 分表攔截器 * @Date 2021/5/9 */@Intercepts({@Signature(type = StatementHandler.class,method = 'prepare',args = {Connection.class, Integer.class})})public class TableShardInterceptor implements Interceptor { private static final ReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory(); @Override public Object intercept(Invocation invocation) throws Throwable {// MetaObject是mybatis里面提供的一個(gè)工具類,類似反射的效果MetaObject metaObject = getMetaObject(invocation);BoundSql boundSql = (BoundSql) metaObject.getValue('delegate.boundSql');MappedStatement mappedStatement = (MappedStatement)metaObject.getValue('delegate.mappedStatement');//獲取Mapper執(zhí)行方法Method method = invocation.getMethod();//獲取分表注解TableShard tableShard = getTableShard(method,mappedStatement);// 如果method與class都沒有TableShard注解或執(zhí)行方法不存在,執(zhí)行下一個(gè)插件邏輯if (tableShard == null) { return invocation.proceed();}//獲取值String value = tableShard.value();//value是否字段名,如果是,需要解析請求參數(shù)字段名的值boolean fieldFlag = tableShard.fieldFlag();if (fieldFlag) { //獲取請求參數(shù) Object parameterObject = boundSql.getParameterObject(); if (parameterObject instanceof MapperMethod.ParamMap) { //ParamMap類型邏輯處理MapperMethod.ParamMap parameterMap = (MapperMethod.ParamMap) parameterObject;//根據(jù)字段名獲取參數(shù)值Object valueObject = parameterMap.get(value);if (valueObject == null) { throw new RuntimeException(String.format('入?yún)⒆侄?s無匹配', value));}//替換sqlreplaceSql(tableShard, valueObject, metaObject, boundSql); } else { //單參數(shù)邏輯//如果是基礎(chǔ)類型拋出異常if (isBaseType(parameterObject)) { throw new RuntimeException('單參數(shù)非法,請使用@Param注解');}if (parameterObject instanceof Map){ Map<String,Object> parameterMap = (Map<String,Object>)parameterObject; Object valueObject = parameterMap.get(value); //替換sql replaceSql(tableShard, valueObject, metaObject, boundSql);} else { //非基礎(chǔ)類型對象 Class<?> parameterObjectClass = parameterObject.getClass(); Field declaredField = parameterObjectClass.getDeclaredField(value); declaredField.setAccessible(true); Object valueObject = declaredField.get(parameterObject); //替換sql replaceSql(tableShard, valueObject, metaObject, boundSql);} }} else {//無需處理parameterField //替換sql replaceSql(tableShard, value, metaObject, boundSql);}//執(zhí)行下一個(gè)插件邏輯return invocation.proceed(); } @Override public Object plugin(Object target) {// 當(dāng)目標(biāo)類是StatementHandler類型時(shí),才包裝目標(biāo)類,否者直接返回目標(biāo)本身, 減少目標(biāo)被代理的次數(shù)if (target instanceof StatementHandler) { return Plugin.wrap(target, this);} else { return target;} } /** * @param object * @methodName: isBaseType * @author: 程序員阿星 * @description: 基本數(shù)據(jù)類型驗(yàn)證,true是,false否 * @date: 2021/5/9 * @return: boolean */ private boolean isBaseType(Object object) {if (object.getClass().isPrimitive()|| object instanceof String|| object instanceof Integer|| object instanceof Double|| object instanceof Float|| object instanceof Long|| object instanceof Boolean|| object instanceof Byte|| object instanceof Short) { return true;} else { return false;} } /** * @param tableShard 分表注解 * @param value 值 * @param metaObject mybatis反射對象 * @param boundSql sql信息對象 * @author: 程序猿阿星 * @description: 替換sql * @date: 2021/5/9 * @return: void */ private void replaceSql(TableShard tableShard, Object value, MetaObject metaObject, BoundSql boundSql) {String tableNamePrefix = tableShard.tableNamePrefix();//獲取策略classClass<? extends ITableShardStrategy> strategyClazz = tableShard.shardStrategy();//從spring ioc容器獲取策略類ITableShardStrategy tableShardStrategy = SpringUtil.getBean(strategyClazz);//生成分表名String shardTableName = tableShardStrategy.generateTableName(tableNamePrefix, value);// 獲取sqlString sql = boundSql.getSql();// 完成表名替換metaObject.setValue('delegate.boundSql.sql', sql.replaceAll(tableNamePrefix, shardTableName)); } /** * @param invocation * @author: 程序猿阿星 * @description: 獲取MetaObject對象-mybatis里面提供的一個(gè)工具類,類似反射的效果 * @date: 2021/5/9 * @return: org.apache.ibatis.reflection.MetaObject */ private MetaObject getMetaObject(Invocation invocation) {StatementHandler statementHandler = (StatementHandler) invocation.getTarget();// MetaObject是mybatis里面提供的一個(gè)工具類,類似反射的效果MetaObject metaObject = MetaObject.forObject(statementHandler,SystemMetaObject.DEFAULT_OBJECT_FACTORY,SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,defaultReflectorFactory);return metaObject; } /** * @author: 程序猿阿星 * @description: 獲取分表注解 * @param method * @param mappedStatement * @date: 2021/5/9 * @return: com.xing.shard.interceptor.TableShard */ private TableShard getTableShard(Method method, MappedStatement mappedStatement) throws ClassNotFoundException {String id = mappedStatement.getId();//獲取Classfinal String className = id.substring(0, id.lastIndexOf('.'));//分表注解TableShard tableShard = null;//獲取Mapper執(zhí)行方法的TableShard注解tableShard = method.getAnnotation(TableShard.class);//如果方法沒有設(shè)置注解,從Mapper接口上面獲取TableShard注解if (tableShard == null) { // 獲取TableShard注解 tableShard = Class.forName(className).getAnnotation(TableShard.class);}return tableShard; }}

到了這里,其實(shí)分表功能就已經(jīng)完成了,我們只需要把分表策略抽象接口、分表注解、分表攔截器抽成一個(gè)通用jar包,需要使用的項(xiàng)目引入這個(gè)jar,然后注冊分表攔截器,自己根據(jù)業(yè)務(wù)需求實(shí)現(xiàn)分表策略,在給對應(yīng)的Mpaaer加上分表注解就好了。

Mybatis實(shí)現(xiàn)分表插件

實(shí)踐跑起來

這里阿星單獨(dú)寫了一套demo,場景是有兩個(gè)分表策略,表也提前建立好了

根據(jù)id分表 tb_log_id_0 tb_log_id_1 根據(jù)日期分表 tb_log_date_202105 tb_log_date_202106

預(yù)警:后面都是代碼實(shí)操環(huán)節(jié),請各位讀者大大耐心看完(非Java開發(fā)除外)。

TableShardStrategy定義

/** * @Author wx * @Description 分表策略日期 * @Date 2021/5/9 */@Componentpublic class TableShardStrategyDate implements ITableShardStrategy { private static final String DATE_PATTERN = 'yyyyMM'; @Override public String generateTableName(String tableNamePrefix, Object value) {verificationTableNamePrefix(tableNamePrefix);if (value == null || StrUtil.isBlank(value.toString())) { return tableNamePrefix + '_' +DateUtil.format(new Date(), DATE_PATTERN);} else { return tableNamePrefix + '_' +DateUtil.format(new Date(), value.toString());} }}** * @Author 程序猿阿星 * @Description 分表策略id * @Date 2021/5/9 */@Componentpublic class TableShardStrategyId implements ITableShardStrategy { @Override public String generateTableName(String tableNamePrefix, Object value) {verificationTableNamePrefix(tableNamePrefix);if (value == null || StrUtil.isBlank(value.toString())) { throw new RuntimeException('value is null');}long id = Long.parseLong(value.toString());//可以加入本地緩存優(yōu)化return tableNamePrefix + '_' + (id % 2); }}Mapper定義

Mapper接口

/** * @Author 程序猿阿星 * @Description * @Date 2021/5/8 */@TableShard(tableNamePrefix = 'tb_log_date',shardStrategy = TableShardStrategyDate.class)public interface LogDateMapper { /** * 查詢列表-根據(jù)日期分表 */ List<LogDate> queryList(); /** * 單插入-根據(jù)日期分表 */ void save(LogDate logDate);}-------------------------------------------------------------------------------------------------/** * @Author 程序猿阿星 * @Description * @Date 2021/5/8 */@TableShard(tableNamePrefix = 'tb_log_id',value = 'id',fieldFlag = true,shardStrategy = TableShardStrategyId.class)public interface LogIdMapper { /** * 根據(jù)id查詢-根據(jù)id分片 */ LogId queryOne(@Param('id') long id); /** * 單插入-根據(jù)id分片 */ void save(LogId logId);}

Mapper.xml

<?xml version='1.0' encoding='UTF-8' ?><!DOCTYPE mapperPUBLIC '-//mybatis.org//DTD Mapper 3.0//EN''http://mybatis.org/dtd/mybatis-3-mapper.dtd'><mapper namespace='com.xing.shard.mapper.LogDateMapper'>//對應(yīng)LogDateMapper#queryList函數(shù) <select resultType='com.xing.shard.entity.LogDate'>selectid as id,comment as comment,create_date as createDatefromtb_log_date </select>//對應(yīng)LogDateMapper#save函數(shù) <insert >insert into tb_log_date(id, comment,create_date)values (#{id}, #{comment},#{createDate}) </insert></mapper>-------------------------------------------------------------------------------------------------<?xml version='1.0' encoding='UTF-8' ?><!DOCTYPE mapperPUBLIC '-//mybatis.org//DTD Mapper 3.0//EN''http://mybatis.org/dtd/mybatis-3-mapper.dtd'><mapper namespace='com.xing.shard.mapper.LogIdMapper'>//對應(yīng)LogIdMapper#queryOne函數(shù) <select resultType='com.xing.shard.entity.LogId'>selectid as id,comment as comment,create_date as createDatefromtb_log_idwhereid = #{id} </select>//對應(yīng)save函數(shù) <insert >insert into tb_log_id(id, comment,create_date)values (#{id}, #{comment},#{createDate}) </insert></mapper>執(zhí)行下單元測試

日期分表單元測試執(zhí)行

@Test void test() {LogDate logDate = new LogDate();logDate.setId(snowflake.nextId());logDate.setComment('測試內(nèi)容');logDate.setCreateDate(new Date());//插入logDateMapper.save(logDate);//查詢List<LogDate> logDates = logDateMapper.queryList();System.out.println(JSONUtil.toJsonPrettyStr(logDates)); }

輸出結(jié)果

Mybatis實(shí)現(xiàn)分表插件

id分表單元測試執(zhí)行

@Test void test() {LogId logId = new LogId();long id = snowflake.nextId();logId.setId(id);logId.setComment('測試');logId.setCreateDate(new Date());//插入logIdMapper.save(logId);//查詢LogId logIdObject = logIdMapper.queryOne(id);System.out.println(JSONUtil.toJsonPrettyStr(logIdObject)); }

輸出結(jié)果

Mybatis實(shí)現(xiàn)分表插件

小結(jié)一下

本文可以當(dāng)做對Mybatis進(jìn)階的使用教程,通過Mybatis攔截器實(shí)現(xiàn)分表的功能,滿足基本的業(yè)務(wù)需求,雖然比較簡陋,但是Mybatis這種擴(kuò)展機(jī)制與設(shè)計(jì)值得學(xué)習(xí)思考。

有興趣的讀者也可以自己寫一個(gè),或基于阿星的做改造,畢竟是簡陋版本,還是有很多場景沒有考慮到。

另外分表的demo項(xiàng)目,阿星放到了Gitee,大家按需自取

Gitee地址: https://gitee.com/jxncwx/shard

項(xiàng)目結(jié)構(gòu):

Mybatis實(shí)現(xiàn)分表插件

到此這篇關(guān)于Mybatis實(shí)現(xiàn)分表插件的文章就介紹到這了,更多相關(guān)Mybatis 分表插件內(nèi)容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Mybatis 數(shù)據(jù)庫
相關(guān)文章:
主站蜘蛛池模板: 一区二区欧美视频 | 久久精品视频免费看 | 69成人做爰免费视频 | 手机日韩看片 | 久久精品中文字幕第一页 | 午夜丝袜美腿福利视频在线看 | 一级片一级毛片 | 99re最新地址获取精品 | 杨幂国产精品福利在线观看 | 看一级片| 久久精品免看国产 | 久久精品亚瑟全部免费观看 | 国产一级片网址 | 国产精品福利片免费看 | 中文精品爱久久久国产 | 男女污污网站 | 免费国产成人综合 | 青青草97国产精品免费观看 | 亚洲精品久久久久中文字小说 | 香蕉性视频 | 久久www免费人成看片入口 | 亚洲四区 | 成人中文字幕在线高清 | 老妇综合久久香蕉蜜桃 | 亚洲特级aaaaaa毛片 | 免费视频爱爱太爽了 | 精品免费视频 | 国产毛片视频网站 | 色综合久久亚洲国产日韩 | 午夜一级免费视频 | 在线免费看一级片 | 麻豆视传媒短视频网站-欢迎您 | 欧美一区二区三区在观看 | 国内特级毛片 | 西西人体www303sw大胆高清 | 亚洲国产成人99精品激情在线 | 亚洲第一免费视频 | 日韩黄| 国内精品久久久久影院嫩草 | 国产精选在线播放 | 国产高清网站 |