>  기사  >  Java  >  springboot+mybatis 인터셉터를 사용하여 수평 테이블 분할을 구현하는 방법

springboot+mybatis 인터셉터를 사용하여 수평 테이블 분할을 구현하는 방법

WBOY
WBOY앞으로
2023-05-14 18:43:171339검색

MyBatis는 플러그인을 사용하여 메서드를 가로채는 것을 허용합니다

  • Executor(update, query, flashStatements, commit, Rollback, getTransaction, close, isClosed)

  • ParameterHandler(getParameterObject, setParameters)

  • ResultSetHandler( ResultSets, handlerOutputParameters 처리 )

  • StatementHandler(준비, 매개변수화, 배치, 업데이트, 쿼리)

전체 요약은 다음과 같습니다.

  • 실행기를 가로채는 방법

  • 가로채기 처리 이온 매개변수

  • 집합 결과 차단

  • SQL 구문 구성 처리 차단

이 네 가지 메소드는 MyBatis의 작업(추가, 삭제, 수정, 쿼리)에서 실행됩니다. 실행 순서는 Executor, ParameterHandler입니다. , ResultSetHandler, StateHandler.

springboot+mybatis 인터셉터를 사용하여 수평 테이블 분할을 구현하는 방법

인터셉터 인터페이스

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는 인터셉터 인터페이스를 제공합니다. 앞서 언급했듯이 우리는 이 인터페이스를 구현하여 자체 인터셉터를 정의할 수 있습니다. 사용자 정의 인터셉터는 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();
    }

}
어떤 종류의 개체를 가로채야 하는지

인터셉터가 전역적으로 가로채기 때문에 가로채야 하는 매퍼만 가로채면 되므로 주석을 사용하여 표시해야 합니다.

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 인터셉터를 사용하여 수평 테이블 분할을 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제