Maison  >  Article  >  Java  >  Comment utiliser l'intercepteur springboot+mybatis pour implémenter le fractionnement horizontal des tables

Comment utiliser l'intercepteur springboot+mybatis pour implémenter le fractionnement horizontal des tables

WBOY
WBOYavant
2023-05-14 18:43:171392parcourir

MyBatis permet l'utilisation de plug-ins pour intercepter les méthodes

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

  • ParameterHandler (getParameterObject, setParameters)

  • ResultSetHandler ( gérer ResultSets, handleOutputParameters)

  • StatementHandler (préparer, paramétrer, batch, mettre à jour, interroger)

Le résumé général est :

  • Méthodes pour intercepter l'exécuteur

  • Traitement de paramètres d'interception

  • Résultats de l'interception Traitement des ensembles

  • Interception du traitement de construction de la syntaxe SQL

Ces quatre méthodes seront exécutées dans une opération de MyBatis (ajouter, supprimer, modifier, requête L'ordre d'exécution est Executor, ParameterHandler). , ResultSetHandler, StatementHandler.

Comment utiliser lintercepteur springboot+mybatis pour implémenter le fractionnement horizontal des tables

Interface de l'intercepteur

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

}

Implémentation de la sous-table

1. Idée générale

La structure de la sous-table a été prédéfinie, nous n'avons donc plus besoin de verrouiller directement la table cible qu'une seule fois lors de l'ajout, de la suppression , la modification et l'interrogation, puis Remplacer le SQL cible.

2. Implémentation étape par étape

Comment Mybatis trouve-t-il notre nouveau service d'interception

Pour l'intercepteur, Mybatis nous fournit une interface Interceptor Comme mentionné précédemment, nous pouvons définir notre propre intercepteur en implémentant cette interface. L'intercepteur personnalisé doit être géré par Mybatis, afin que l'exécution de Mybatis puisse être combinée avec l'exécution de l'intercepteur, c'est-à-dire utiliser Springboot pour injecter l'intercepteur personnalisé.

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

}
Quels types d'objets doivent être interceptés

Parce que l'intercepteur intercepte globalement, nous n'avons besoin que d'intercepter le mappeur que nous devons intercepter, nous devons donc utiliser des annotations pour l'identifier

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;

}

Il nous suffit de marquer cette annotation dans l'objet que nous voulons intercepter Implémenter un intercepteur personnalisé sur le mappeur

@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;
    }
}

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer