亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么使用springboot+mybatis攔截器實現水平分表

發布時間:2022-08-10 16:27:30 來源:億速云 閱讀:155 作者:iii 欄目:開發技術

這篇文章主要介紹了怎么使用springboot+mybatis攔截器實現水平分表的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇怎么使用springboot+mybatis攔截器實現水平分表文章都會有所收獲,下面我們一起來看看吧。

MyBatis 允許使用插件來攔截的方法

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

  • ParameterHandler (getParameterObject, setParameters)

  • ResultSetHandler (handleResultSets, handleOutputParameters)

  • StatementHandler (prepare, parameterize, batch, update, query)

總體概括為:

  • 攔截執行器的方法

  • 攔截參數的處理

  • 攔截結果集的處理

  • 攔截Sql語法構建的處理

這4各方法在MyBatis的一個操作(新增,刪除,修改,查詢)中都會被執行到,執行的先后順序是Executor,ParameterHandler,ResultSetHandler,StatementHandler。

怎么使用springboot+mybatis攔截器實現水平分表

Interceptor接口

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);

}

分表實現

1、大體思路

分表的表結構已經預設完畢,所以現在我們只需要在進行增刪改查的時候直接一次鎖定目標表,然后替換目標sql。

2、逐步實現

Mybatis如何找到我們新增的攔截服務

對于攔截器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攔截器實現水平分表”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

溆浦县| 舟曲县| 大厂| 洞头县| 昭平县| 江北区| 仁怀市| 泰和县| 长治县| 无极县| 余庆县| 海淀区| 武穴市| 武乡县| 沂南县| 迁安市| 南安市| 波密县| 克什克腾旗| 隆化县| 淳安县| 抚顺市| 溧水县| 五河县| 苏州市| 卓资县| 铜山县| 滦南县| 绿春县| 罗平县| 衡阳县| 诸城市| 东兴市| 南召县| 桐庐县| 格尔木市| 荔波县| 天长市| 板桥市| 新津县| 灌云县|