<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
業務飛速發展導致了資料規模的急速膨脹,單機資料庫已經無法適應網際網路業務的發展。由於MySQL採用 B+樹索引,資料量超過閾值時,索引深度的增加也將使得磁碟存取的 IO 次數增加,進而導致查詢效能的下降;高並行存取請求也使得集中式資料庫成為系統的最大瓶頸。我們團隊結合公司業務背景商議最終選定一份表的形式進行解決這一瓶頸問題,由我作為主要開發主導完成,故寫篇部落格記錄沉澱。
總體概括為:
這4各方法在MyBatis的一個操作(新增,刪除,修改,查詢)中都會被執行到,執行的先後順序是Executor,ParameterHandler,ResultSetHandler,StatementHandler。
package org.apache.ibatis.plugin; import java.util.Properties; public interface Interceptor { //intercept方法就是要進行攔截的時候要執行的方法。 Object intercept(Invocation invocation) throws Throwable; //plugin方法是攔截器用於封裝目標物件的,通過該方法我們可以返回目標物件本身,也可以返回一個它的代理。當返回的是代理的時候我們可以對其中的方法進行攔截來呼叫intercept方法,當然也可以呼叫其他方法。 Object plugin(Object target); //setProperties方法是用於在Mybatis組態檔中指定一些屬性的。 void setProperties(Properties properties); }
分表的表結構已經預設完畢,所以現在我們只需要在進行增刪改查的時候直接一次鎖定目標表,然後替換目標sql。
對於攔截器Mybatis為我們提供了一個Interceptor介面,前面有提到,通過實現該介面就可以定義我們自己的攔截器。自定義的攔截器需要交給Mybatis管理,這樣才能使得Mybatis的執行與攔截器的執行結合在一起,即,利用springboot把自定義攔截器注入。
package com.shinemo.insurance.common.config; import org.apache.ibatis.plugin.Interceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class TableShardConfig { /** * 註冊外掛 */ @Bean public Interceptor tableShardInterceptor() { return new TableShardInterceptor(); } }
因為攔截器是全域性攔截的,我們只需要攔截我們需要攔截的mapper,故需要用註解進行標識
package com.shinemo.insurance.common.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(value = { ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface TableShard { // 表字首名 String tableNamePrefix(); // 值 String value() default ""; // 是否是欄位名,如果是需要解析請求引數改欄位名的值(預設否) boolean fieldFlag() default false; }
我們只需要把這個註解標識在我們要攔截的mapper上
@Mapper @TableShard(tableNamePrefix = "t_insurance_video_people_", value = "deviceId", fieldFlag = true) public interface InsuranceVideoPeopleMapper { //VideoPeople物件中包含deviceId欄位 int insert(VideoPeople videoPeople); }
package com.shinemo.insurance.common.config; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.sql.Connection; import java.util.Map; import com.shinemo.insurance.common.annotation.TableShard; import com.shinemo.insurance.common.util.HashUtil; import org.apache.ibatis.binding.MapperMethod; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.reflection.DefaultReflectorFactory; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.ReflectorFactory; import org.apache.ibatis.reflection.SystemMetaObject; @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) }) public class TableShardInterceptor implements Interceptor { private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory(); @Override public Object intercept(Invocation invocation) throws Throwable { // MetaObject是mybatis裡面提供的一個工具類,類似反射的效果 MetaObject metaObject = getMetaObject(invocation); BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql"); MappedStatement mappedStatement = (MappedStatement) metaObject .getValue("delegate.mappedStatement"); // 獲取Mapper執行方法 Method method = invocation.getMethod(); // 獲取分表註解 TableShard tableShard = getTableShard(method, mappedStatement); // 如果method與class都沒有TableShard註解或執行方法不存在,執行下一個外掛邏輯 if (tableShard == null) { return invocation.proceed(); } //獲取值,此值就是拿的註解上value值,註解上value設定的值,並在傳入物件中獲取,根據業務可以選擇適當的值即可,我選取此值的目的是同一臺裝置的值存入一張表中,有hash衝突的值也存在一張表中 String value = tableShard.value(); //value是否欄位名,如果是,需要解析請求引數欄位名的值 boolean fieldFlag = tableShard.fieldFlag(); if (fieldFlag) { //獲取請求引數 Object parameterObject = boundSql.getParameterObject(); if (parameterObject instanceof MapperMethod.ParamMap) { // ParamMap型別邏輯處理 MapperMethod.ParamMap parameterMap = (MapperMethod.ParamMap) parameterObject; // 根據欄位名獲取引數值 Object valueObject = parameterMap.get(value); if (valueObject == null) { throw new RuntimeException(String.format("入參欄位%s無匹配", value)); } //替換sql replaceSql(tableShard, valueObject, metaObject, boundSql); } else { // 單引數邏輯 //如果是基礎型別丟擲異常 if (isBaseType(parameterObject)) { throw new RuntimeException("單引數非法,請使用@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 { //非基礎型別物件 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); } //把原有的簡單查詢語句替換為分表查詢語句了,現在是時候將程式的控制權交還給Mybatis下一個攔截器處理 return invocation.proceed(); } /** * @description: * @param target * @return: Object */ @Override public Object plugin(Object target) { // 當目標類是StatementHandler型別時,才包裝目標類,否者直接返回目標本身, 減少目標被代理的次數 if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } else { return target; } } /** * @description: 基本資料型別驗證,true是,false否 * @param object * @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; } } /** * @description: 替換sql * @param tableShard 分表註解 * @param value 值 * @param metaObject mybatis反射物件 * @param boundSql sql資訊物件 * @return: void */ private void replaceSql(TableShard tableShard, Object value, MetaObject metaObject, BoundSql boundSql) { String tableNamePrefix = tableShard.tableNamePrefix(); // // 獲取策略class // Class<? extends ITableShardStrategy> strategyClazz = tableShard.shardStrategy(); // // 從spring ioc容器獲取策略類 // ITableShardStrategy tableShardStrategy = SpringBeanUtil.getBean(strategyClazz); // 生成分表名 String shardTableName = generateTableName(tableNamePrefix, (String) value); // 獲取sql String sql = boundSql.getSql(); // 完成表名替換 metaObject.setValue("delegate.boundSql.sql", sql.replaceAll(tableNamePrefix, shardTableName)); } /** * 生成表名 * * @param tableNamePrefix 表名字首 * @param value 價值 * @return {@link String} */ private String generateTableName(String tableNamePrefix, String value) { //我們分了1024張表 int prime = 1024; //hash取模運算過後,鎖定目標表 int rotatingHash = HashUtil.rotatingHash(value, prime); return tableNamePrefix + rotatingHash; } /** * @description: 獲取MetaObject物件-mybatis裡面提供的一個工具類,類似反射的效果 * @param invocation * @return: MetaObject */ private MetaObject getMetaObject(Invocation invocation) { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); // MetaObject是mybatis裡面提供的一個工具類,類似反射的效果 MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY); return metaObject; } /** * @description: 獲取分表註解 * @param method * @param mappedStatement * @return: TableShard */ private TableShard getTableShard(Method method, MappedStatement mappedStatement) throws ClassNotFoundException { String id = mappedStatement.getId(); // 獲取Class final String className = id.substring(0, id.lastIndexOf(".")); // 分表註解 TableShard tableShard = null; // 獲取Mapper執行方法的TableShard註解 tableShard = method.getAnnotation(TableShard.class); // 如果方法沒有設定註解,從Mapper介面上面獲取TableShard註解 if (tableShard == null) { // 獲取TableShard註解 tableShard = Class.forName(className).getAnnotation(TableShard.class); } return tableShard; } }
到此這篇關於springboot+mybatis攔截器方法實現水平分表操作的文章就介紹到這了,更多相關springboot mybatis水平分表操作內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45