您好,登錄后才能下訂單哦!
這篇文章主要介紹了怎么使用springboot+mybatis攔截器實(shí)現(xiàn)水平分表的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇怎么使用springboot+mybatis攔截器實(shí)現(xiàn)水平分表文章都會(huì)有所收獲,下面我們一起來看看吧。
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
總體概括為:
攔截執(zhí)行器的方法
攔截參數(shù)的處理
攔截結(jié)果集的處理
攔截Sql語(yǔ)法構(gòu)建的處理
這4各方法在MyBatis的一個(gè)操作(新增,刪除,修改,查詢)中都會(huì)被執(zhí)行到,執(zhí)行的先后順序是Executor,ParameterHandler,ResultSetHandler,StatementHandler。
package org.apache.ibatis.plugin; import java.util.Properties; public interface Interceptor { //intercept方法就是要進(jìn)行攔截的時(shí)候要執(zhí)行的方法。 Object intercept(Invocation invocation) throws Throwable; //plugin方法是攔截器用于封裝目標(biāo)對(duì)象的,通過該方法我們可以返回目標(biāo)對(duì)象本身,也可以返回一個(gè)它的代理。當(dāng)返回的是代理的時(shí)候我們可以對(duì)其中的方法進(jìn)行攔截來調(diào)用intercept方法,當(dāng)然也可以調(diào)用其他方法。 Object plugin(Object target); //setProperties方法是用于在Mybatis配置文件中指定一些屬性的。 void setProperties(Properties properties); }
分表的表結(jié)構(gòu)已經(jīng)預(yù)設(shè)完畢,所以現(xiàn)在我們只需要在進(jìn)行增刪改查的時(shí)候直接一次鎖定目標(biāo)表,然后替換目標(biāo)sql。
對(duì)于攔截器Mybatis為我們提供了一個(gè)Interceptor接口,前面有提到,通過實(shí)現(xiàn)該接口就可以定義我們自己的攔截器。自定義的攔截器需要交給Mybatis管理,這樣才能使得Mybatis的執(zhí)行與攔截器的執(zhí)行結(jié)合在一起,即,利用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 { /** * 注冊(cè)插件 */ @Bean public Interceptor tableShardInterceptor() { return new TableShardInterceptor(); } }
因?yàn)閿r截器是全局?jǐn)r截的,我們只需要攔截我們需要攔截的mapper,故需要用注解進(jìn)行標(biāo)識(shí)
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 ""; // 是否是字段名,如果是需要解析請(qǐng)求參數(shù)改字段名的值(默認(rèn)否) boolean fieldFlag() default false; }
我們只需要把這個(gè)注解標(biāo)識(shí)在我們要攔截的mapper上
@Mapper @TableShard(tableNamePrefix = "t_insurance_video_people_", value = "deviceId", fieldFlag = true) public interface InsuranceVideoPeopleMapper { //VideoPeople對(duì)象中包含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里面提供的一個(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(); } //獲取值,此值就是拿的注解上value值,注解上value設(shè)定的值,并在傳入對(duì)象中獲取,根據(jù)業(yè)務(wù)可以選擇適當(dāng)?shù)闹导纯?,我選取此值的目的是同一臺(tái)設(shè)備的值存入一張表中,有hash沖突的值也存在一張表中 String value = tableShard.value(); //value是否字段名,如果是,需要解析請(qǐng)求參數(shù)字段名的值 boolean fieldFlag = tableShard.fieldFlag(); if (fieldFlag) { //獲取請(qǐng)求參數(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)); } //替換sql replaceSql(tableShard, valueObject, metaObject, boundSql); } else { // 單參數(shù)邏輯 //如果是基礎(chǔ)類型拋出異常 if (isBaseType(parameterObject)) { throw new RuntimeException("單參數(shù)非法,請(qǐng)使用@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ǔ)類型對(duì)象 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); } //把原有的簡(jiǎn)單查詢語(yǔ)句替換為分表查詢語(yǔ)句了,現(xiàn)在是時(shí)候?qū)⒊绦虻目刂茩?quán)交還給Mybatis下一個(gè)攔截器處理 return invocation.proceed(); } /** * @description: * @param target * @return: Object */ @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; } } /** * @description: 基本數(shù)據(jù)類型驗(yàn)證,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反射對(duì)象 * @param boundSql sql信息對(duì)象 * @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 價(jià)值 * @return {@link String} */ private String generateTableName(String tableNamePrefix, String value) { //我們分了1024張表 int prime = 1024; //hash取模運(yùn)算過后,鎖定目標(biāo)表 int rotatingHash = HashUtil.rotatingHash(value, prime); return tableNamePrefix + rotatingHash; } /** * @description: 獲取MetaObject對(duì)象-mybatis里面提供的一個(gè)工具類,類似反射的效果 * @param invocation * @return: 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, 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執(zhí)行方法的TableShard注解 tableShard = method.getAnnotation(TableShard.class); // 如果方法沒有設(shè)置注解,從Mapper接口上面獲取TableShard注解 if (tableShard == null) { // 獲取TableShard注解 tableShard = Class.forName(className).getAnnotation(TableShard.class); } return tableShard; } }
關(guān)于“怎么使用springboot+mybatis攔截器實(shí)現(xiàn)水平分表”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“怎么使用springboot+mybatis攔截器實(shí)現(xiàn)水平分表”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。