Componentes personalizados de sub-biblioteca y sub-tabla (componentes que implementan sub-biblioteca y sub-tabla)——java

Directorio de artículos de la serie


prefacio

Primero puede ver el conocimiento básico anterior: por qué la subtabla de la subbase de datos

Este artículo se basa en mis propias notas de estudio organizadas por las notas del hermano Fu. Es solo para aprender. Si hay alguna infracción, comuníquese con

Este artículo es la implementación específica de los componentes de la subbase de datos y la subtabla: lo que queremos lograr es también el diseño de enrutamiento de la división horizontal, como se muestra en la figura.
inserte la descripción de la imagen aquí

1. Tecnología requerida

  • Se trata del uso de la interceptación de aspectos AOP, porque es necesario marcar el método utilizando el enrutamiento de la base de datos para facilitar el procesamiento de la lógica de la subbase y la subtabla.
  • La operación de cambio de fuente de datos, dado que hay subbases de datos, implicará el cambio de enlace entre múltiples fuentes de datos para asignar datos a diferentes bases de datos.
  • La operación de direccionamiento de la tabla de la base de datos, a qué base de datos y a qué tabla se asigna un dato, requiere el cálculo del índice. En el proceso de llamadas a métodos, ThreadLocal finalmente lo registra.
  • Para permitir que los datos se distribuyan uniformemente en diferentes tablas de la base de datos, también es necesario considerar cómo realizar operaciones hash de datos.Después de que la base de datos se divida en tablas, deje que los datos se concentren en una tabla determinada en una base de datos determinada, de modo que que los datos se perderán La importancia de la subtabla de la subbase de datos.
    En resumen, se puede ver que el almacenamiento de datos se completa bajo la estructura de datos de la base de datos y la tabla.Las tecnologías que necesito usar incluyen: AOP、数据源切换、散列算法、哈希寻址、ThreadLocal以及SpringBoot的Starter开发方式y otras tecnologías. Y como hashing, direccionamiento y almacenamiento de datos, de hecho, estas tecnologías tienen demasiadas similitudes con HashMap.

2. Resumen técnico

1. Tema Local

inserte la descripción de la imagen aquí

@Test
public void test_idx() {
    
    
    int hashCode = 0;
    for (int i = 0; i < 16; i++) {
    
    
        hashCode = i * 0x61c88647 + 0x61c88647;
        int idx = hashCode & 15;
        System.out.println("斐波那契散列:" + idx + " 普通散列:" + (String.valueOf(i).hashCode() & 15));
    }
} 

斐波那契散列:7 普通散列:0
斐波那契散列:14 普通散列:1
斐波那契散列:5 普通散列:2
斐波那契散列:12 普通散列:3
斐波那契散列:3 普通散列:4
斐波那契散列:10 普通散列:5
斐波那契散列:1 普通散列:6
斐波那契散列:8 普通散列:7
斐波那契散列:15 普通散列:8
斐波那契散列:6 普通散列:9
斐波那契散列:13 普通散列:15
斐波那契散列:4 普通散列:0
斐波那契散列:11 普通散列:1
斐波那契散列:2 普通散列:2
斐波那契散列:9 普通散列:3
斐波那契散列:0 普通散列:4

2. Mapa hash

inserte la descripción de la imagen aquí

public static int disturbHashIdx(String key, int size) {
    
    
    return (size - 1) & (key.hashCode() ^ (key.hashCode() >>> 16));
}

Tres, date cuenta

1. Definir anotaciones de enrutamiento

anotación personalizada

  • Concepto : Describe el procedimiento.
    Notas para computadoras : Describa los programas con palabras. Definición para programadores
    : Anotación, también llamada metadatos. Una especificación a nivel de código. Es una característica introducida por JDK1.5 y versiones posteriores, y está al mismo nivel que las clases, las interfaces y las enumeraciones. Puede declararse delante de paquetes, clases, campos, métodos, variables locales, parámetros de métodos, etc., para explicar y comentar estos elementos. Descripción del concepto: Las nuevas funciones posteriores a JDK1.5 explican
    el uso del programa Anotación: @ nombre de anotación

Formato

meta-anotación public @interface nombre-anotación { lista-propiedad; }

Una anotación es esencialmente una interfaz, que hereda la interfaz de anotación de forma predeterminada.

public interface MyAnno extends java.lang.annotation.Annotation {}
Las interfaces pueden tener métodos abstractos

Requerir

1. El tipo de valor de retorno del atributo tiene los siguientes valores: tipo de datos básicos, cadena, enumeración y matriz de los tipos de anotaciones anteriores 2. Después de definir
el atributo, debe asignarle un valor al
usarlo 3. Al definir el atributo, use defaultpalabras clave para dar el valor de inicialización predeterminado de la propiedad, al usar la anotación, no es necesario asignar la propiedad.
4. Si solo se necesita asignar un atributo y el nombre del atributo es valor, el valor se puede omitir y el valor se puede definir directamente.
Al asignar una matriz, el valor se envuelve con {}. {} se puede omitir si solo hay un valor en la matriz

定义:
public @interface MyAnno {
    
    
    int value();
    Person per();
    MyAnno2 anno2();
    String[] strs();
}

public enum Person {
    
    

    P1,P2;
}

使用:
@MyAnno(value=12,per = Person.P1,anno2 = @MyAnno2,strs="bbb")
public class Worker {
    
    

}

meta-anotaciones: Anotaciones utilizadas para describir anotaciones

@Target: Describa la posición en la que puede actuar la anotación
ElementTypeValor:
TYPE: Puede actuar sobre la clase
METHOD: Puede actuar sobre el método
FIELD: Puede actuar sobre la variable miembro:
@RetentionDescriba la etapa en la que se retiene la anotación
@Retention(RetentionPolicy.RUNTIME): La anotación actualmente descrita se retendrá en la clase bytecode En el archivo y leído por la JVM, las anotaciones personalizadas generalmente usan esto.
@Documented: si la anotación de descripción se extrae en el documento api
@Inherited: si las subclases heredan la anotación de descripción

Usar (analizar) anotaciones en el programa: obtener los valores de atributos definidos en las anotaciones

Ejemplos de reflexiones anteriores

/**
前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
*/

//1.加载配置文件
//1.1创建Properties对象
Properties pro = new Properties();
//1.2加载配置文件,转换为一个集合
//1.2.1获取class目录下的配置文件
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("pro.properties");
pro.load(is);

//2.获取配置文件中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");


//3.加载该类进内存
Class cls = Class.forName(className);
//4.创建对象
Object obj = cls.newInstance();
//5.获取方法对象
Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke(obj);

En reflexión, se pueden crear objetos de cualquier clase mediante la lectura de archivos de configuración y se pueden ejecutar métodos arbitrarios.
Podemos reemplazar las operaciones anteriores relacionadas con la lectura de archivos de configuración a través de anotaciones. El código específico es el siguiente: Las anotaciones se definen de la siguiente manera:

/**
 * 描述需要执行的类名,和方法名
 * @author ymj
 */

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
    
    

    String className();
    String methodName();
}

Al analizar la configuración de anotaciones, realizar la creación de objetos relacionados y ejecutar métodos de objetos.

  • Obtenga el objeto de la ubicación definida por la anotación (Clase, Método, Campo)
  • Obtener la anotación especificada
  • Llame al método abstracto en la anotación para obtener el valor del atributo configurado.
    El código es el siguiente:
@Pro(className = "com.zjq.javabase.base25.annotation.Demo1",methodName = "show")
public class ReflectTest {
    
    
    public static void main(String[] args) throws Exception {
    
    

        /**
         * 前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
         */

        //1.解析注解
        //1.1获取该类的字节码文件对象
        Class<ReflectTest> reflectTestClass = ReflectTest.class;
        //2.获取上边的注解对象
        //其实就是在内存中生成了一个该注解接口的子类实现对象
        /*

            public class ProImpl implements Pro{
                public String className(){
                    return "com.zjq.javabase.base25.annotation.Demo1";
                }
                public String methodName(){
                    return "show";
                }

            }
         */
        Pro an = reflectTestClass.getAnnotation(Pro.class);
        //3.调用注解对象中定义的抽象方法,获取返回值
        String className = an.className();
        String methodName = an.methodName();
        System.out.println(className);
        System.out.println(methodName);


        //4.加载该类进内存
        Class cls = Class.forName(className);
        //5.创建对象
        Object obj = cls.newInstance();
        //6.获取方法对象
        Method method = cls.getMethod(methodName);
        //7.执行方法
        method.invoke(obj);
    }
}

Pequeño ejemplo: las anotaciones definen un marco de prueba simple

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
    
    
}

Defina una clase de herramienta de calculadora y use la anotación @Check en el método

/**
 * 定义的计算器类
 * @author ymj
 */
public class Calculator {
    
    

    //加法
    @Check
    public void add(){
    
    
        String str = null;
        str.toString();
        System.out.println("1 + 0 =" + (1 + 0));
    }
    //减法
    @Check
    public void sub(){
    
    
        System.out.println("1 - 0 =" + (1 - 0));
    }
    //乘法
    @Check
    public void mul(){
    
    
        System.out.println("1 * 0 =" + (1 * 0));
    }
    //除法
    @Check
    public void div(){
    
    
        System.out.println("1 / 0 =" + (1 / 0));
    }

    public void show(){
    
    
        System.out.println("永无bug...");
    }

}

Defina la clase de marco de prueba y ejecute la prueba, y registre la excepción de prueba en el archivo bug.txt.El código es el siguiente:

/**
 * 简单的测试框架
 * 当主方法执行后,会自动自行被检测的所有方法(加了Check注解的方法),判断方法是否有异常,
 * 记录到文件中
 *
 * @author ymj
 */
public class TestCheck {
    
    

    public static void main(String[] args) throws IOException {
    
    
        //1.创建计算器对象
        Calculator c = new Calculator();
        //2.获取字节码文件对象
        Class cls = c.getClass();
        //3.获取所有方法
        Method[] methods = cls.getMethods();

        int number = 0;//出现异常的次数
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));


        for (Method method : methods) {
    
    
            //4.判断方法上是否有Check注解
            if (method.isAnnotationPresent(Check.class)) {
    
    
                //5.有,执行
                try {
    
    
                    method.invoke(c);
                } catch (Exception e) {
    
    
                    //6.捕获异常

                    //记录到文件中
                    number++;

                    bw.write(method.getName() + " 方法出异常了");
                    bw.newLine();
                    bw.write("异常的名称:" + e.getCause().getClass().getSimpleName());
                    bw.newLine();
                    bw.write("异常的原因:" + e.getCause().getMessage());
                    bw.newLine();
                    bw.write("--------------------------");
                    bw.newLine();

                }
            }
        }

        bw.write("本次测试一共出现 " + number + " 次异常");

        bw.flush();
        bw.close();

    }

}

Después de ejecutar la prueba, puede ver el contenido del archivo bug.txt en el directorio del mismo nivel que src de la siguiente manera:

add 方法出异常了
异常的名称:NullPointerException
异常的原因:null
div 方法出异常了
异常的名称:ArithmeticException
异常的原因:/ by zero 

escritura práctica

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
    
    ElementType.TYPE, ElementType.METHOD})
public @interface DBRouter {
    
    

    String key() default "";

}

pequeño resumen

La mayoría de las veces, solo usamos anotaciones, no anotaciones personalizadas.
¿Para quién son las anotaciones?

  • traductor
  • El uso de anotaciones para analizar programas
    no es parte del programa, se puede entender que las anotaciones son una etiqueta.
@Mapper
public interface IUserDao {
    
    

     @DBRouter(key = "userId")
     User queryUserInfoByUserId(User req);

     @DBRouter(key = "userId")
     void insertUser(User req);

}

  • En primer lugar, debemos personalizar una anotación para que se coloque en el método que la base de datos debe enrutar.
  • Su método de uso es a través de anotaciones de configuración del método, que pueden ser interceptadas por el aspecto AOP que especificamos.Después de la intercepción, se realiza el cálculo y el juicio de enrutamiento de la base de datos correspondiente, y se cambia la fuente de datos de operación correspondiente.

2. Análisis de la configuración de enrutamiento

¡Este artículo también está bien escrito,
haga clic aquí! ! ! ! !

Para la configuración de enrutamiento, debe configurar subbases de datos y subtablas para definir múltiples configuraciones de fuentes de datos en su propia aplicación.yml

Configurar la información de las tres bibliotecas

  • Lo anterior es una configuración de fuente de datos después de haber implementado el componente de enrutamiento de la base de datos. En el uso de fuentes de datos en subbases de datos y subtablas, es necesario admitir la configuración de información de múltiples fuentes de datos, para cumplir con la expansión de diferentes necesidades.
  • Para esta gran configuración de información personalizada, debe usar org.springframework.context.EnvironmentAwarela interfaz para obtener el archivo de configuración y extraer la información de configuración requerida.

Para obtener la configuración, debe implementar la interfaz anterior y luego reescribir setEnvironmentel método

Aquí presentamos todas las clases administradas por Spring, que implementan la interfaz EnvironmentAware y reescriben el método setEnvironment para obtener variables de entorno del sistema y variables en el archivo de configuración de la aplicación cuando se inicia el proyecto.
Ejemplo :

package  com.kfit.environment;
  
import  org.springframework.beans.factory.annotation.Value;
import  org.springframework.boot.bind.RelaxedPropertyResolver;
import  org.springframework.context.EnvironmentAware;
import  org.springframework.context.annotation.Configuration;
import  org.springframework.core.env.Environment;
  
/**
  * 主要是@Configuration,实现接口:EnvironmentAware就能获取到系统环境信息;
  *
  * 
  */
@Configuration
public  class  MyEnvironmentAware  implements  EnvironmentAware{
    
    
  
        //注入application.properties的属性到指定变量中.
        @Value ( "${spring.datasource.url}" )
        private  String myUrl;
       
        /**
         *注意重写的方法 setEnvironment 是在系统启动的时候被执行。
         */
        @Override
        public  void  setEnvironment(Environment environment) {
    
    
              
               //打印注入的属性信息.
               System.out.println( "myUrl=" +myUrl);
              
               //通过 environment 获取到系统属性.
               System.out.println(environment.getProperty( "JAVA_HOME" ));
              
               //通过 environment 同样能获取到application.properties配置的属性.
               System.out.println(environment.getProperty( "spring.datasource.url" ));
              
               //获取到前缀是"spring.datasource." 的属性列表值.
               RelaxedPropertyResolver relaxedPropertyResolver =  new  RelaxedPropertyResolver(environment,  "spring.datasource." );
               System.out.println( "spring.datasource.url=" +relaxedPropertyResolver.getProperty( "url" ));
        System.out.println( "spring.datasource.driverClassName=" +relaxedPropertyResolver.getProperty( "driverClassName" ));
        }
}

La información del archivo application.properties es:

 ########################################################
###datasource
########################################################
spring.datasource.url = jdbc:mysql: //localhost:3306/test
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.max-active= 20
spring.datasource.max-idle= 8
spring.datasource.min-idle= 8
spring.datasource.initial-size= 10
@Override
public void setEnvironment(Environment environment) {
    
    
    String prefix = "router.jdbc.datasource.";    
	//prefix,是数据源配置的开头信息,你可以自定义需要的开头内容。
//dbCount 分库数量、tbCount 分表数量、dataSources 数据源、dataSourceProps ,
//都是对配置信息的提取,并存放到 dataSourceMap (数据源配置组)中便于后续使用。
    dbCount = Integer.valueOf(environment.getProperty(prefix + "dbCount"));
    tbCount = Integer.valueOf(environment.getProperty(prefix + "tbCount"));    

    String dataSources = environment.getProperty(prefix + "list");
    for (String dbInfo : dataSources.split(",")) {
    
    
        Map<String, Object> dataSourceProps = PropertyUtil.handle(environment, prefix + dbInfo, Map.class);
        dataSourceMap.put(dbInfo, dataSourceProps);
    }
}

Por supuesto, esta PropertyUtiles la clase de herramienta de operación de archivo de configuración de lectura autodefinida. La función de manejo (según la versión Springboot) salta a sus propios métodos v1 y v2 a través del reflejo en la clase de herramienta. Similar al análisis de configuración de la costumbre anterior anotaciones

3. Cambio de fuente de datos

En el Starter desarrollado en conjunto con SpringBoot, es necesario proporcionar un objeto instanciado de DataSource, luego colocamos este objeto en DataSourceAutoConfig para implementar, y la fuente de datos proporcionada aquí se puede cambiar dinámicamente, es decir, admite el cambio dinámico de datos fuentes.
explicar aquí

El papel de las dos anotaciones

Spring Boot recomienda usar la configuración de Java para reemplazar completamente la configuración de XML. La configuración de Java se implementa a través de las anotaciones @Configuration y @Bean. Ambos funcionan de la siguiente manera:

  • Anotación @Configuration: declara que la clase actual es una clase de configuración, que es equivalente a un archivo XML en Spring
  • Anotación @Bean: Actúa sobre el método y declara que el valor de retorno del método actual es un Bean. Si
    no lo entiende, haga clic aquí . ! ! ! ! ! !

Al mismo tiempo, también debemos ser optimistas sobre la diferencia entre @Component y @Bean

Creación de fuente de datos

@Bean
public DataSource dataSource() {
    
    
    // 创建数据源
    Map<Object, Object> targetDataSources = new HashMap<>();
    for (String dbInfo : dataSourceMap.keySet()) {
    
    
        Map<String, Object> objMap = dataSourceMap.get(dbInfo);
        //new 了一个构造器
        targetDataSources.put(dbInfo, new DriverManagerDataSource(objMap.get("url").toString(), objMap.get("username").toString(), objMap.get("password").toString()));
    }     

    // 设置数据源
    DynamicDataSource dynamicDataSource = new DynamicDataSource();
    //targetDataSources:保存多个数据源的map
//defaultTargetDataSource:指默认的数据源
    dynamicDataSource.setTargetDataSources(targetDataSources);
    dynamicDataSource.setDefaultTargetDataSource(new DriverManagerDataSource(defaultDataSourceConfig.get("url").toString(), defaultDataSourceConfig.get("username").toString(), defaultDataSourceConfig.get("password").toString()));

    return dynamicDataSource;
}

Aquí hay un caso de creación simplificado, basado en la información de la fuente de datos leída de la información de configuración, se crea la creación de instancias.
Dado que hay más de una base de datos, se usa DynamicDataSource
y esta biblioteca DriverManagerDataSource
es solo una forma de conectarse a la base de datos.

Una vez creada la fuente de datos, se almacena DynamicDataSourceen . La clase DynamicDataSource es una clase personalizada en este artículo AbstractRoutingDataSource. Es una clase de implementación heredada de . Esta clase puede almacenar y leer la información de la fuente de datos correspondiente para llamadas específicas.
Artículo de referencia: ¡aquí! ! ! !
targetDataSources: un mapa que guarda múltiples fuentes de datos.
defaultTargetDataSource: se refiere a la fuente de datos predeterminada
. El siguiente es un buen artículo. Primero, targetDataSources es un mapa que guarda diferentes fuentes de datos según la clave. Puede ver en el código fuente que targetDataSources se convertirá en otra variable de mapa, resolveDataSources y defaultTargetDataSource se convertirá en resolveDefaultDataSource

Configuración y uso de fuentes de datos múltiples dinámicas de Springboot (2)

4. Sección de interceptación

Debe completarse en la intercepción del aspecto de AOP ; cálculo de enrutamiento de la base de datos, hash mejorado de la función de perturbación, cálculo del índice de la tabla de la biblioteca, configuración de ThreadLocal para transferir la fuente de datos, el código general del caso es el siguiente: el código del aspecto inicial es agregado con
una recopilación de punto de entrada de Pointcut

Agregar @annotation a Pointcut: se usa para hacer coincidir el método que el método de ejecución actual contiene la anotación especificada

@annotation (tipo de anotación): coincide con la anotación especificada en el método llamado.

el caso

Definir una anotación que se puede utilizar en un método

package com.javacode2018.aop.demo9.test12;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ann12 {
    
    
}

definir 2 clases

S12Parent es la clase principal, que define 2 métodos internamente. Ambos métodos tienen la anotación @Ann12.
S12 es la clase objetivo del agente y una subclase de S12Parent. El método m2 se reescribe internamente. Después de reescribir, no hay @ en m2 método Ann12 anotación, S12 también define dos métodos m3 y m4, y m3 tiene anotación @Ann12
paquete com.javacode2018.aop.demo9.test12;

class S12Parent {
    
    

    @Ann12
    public void m1() {
    
    
        System.out.println("我是S12Parent.m1()方法");
    }

    @Ann12
    public void m2() {
    
    
        System.out.println("我是S12Parent.m2()方法");
    }
}

public class S12 extends S12Parent {
    
    

    @Override
    public void m2() {
    
    
        System.out.println("我是S12.m2()方法");
    }

    @Ann12
    public void m3() {
    
    
        System.out.println("我是S12.m3()方法");
    }

    public void m4() {
    
    
        System.out.println("我是S12.m4()方法");
    }
}

Ven a una clase de Aspect

Cuando el método de destino llamado se anota con @Ann12, beforeAdvice lo procesará.
paquete com.javacode2018.aop.demo9.test12;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectTest12 {
    
    

    @Pointcut("@annotation(com.javacode2018.aop.demo9.test12.Ann12)")
    public void pc() {
    
    
    }

    @Before("pc()")
    public void beforeAdvice(JoinPoint joinPoint) {
    
    
        System.out.println(joinPoint);
    }
}

caso de prueba

S12作为目标对象,创建代理,然后分别调用4个方法
@Test
public void test12() {
    
    
    S12 target = new S12();
    AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
    proxyFactory.setTarget(target);
    proxyFactory.addAspect(AspectTest12.class);
    S12 proxy = proxyFactory.getProxy();
    proxy.m1();
    proxy.m2();
    proxy.m3();
    proxy.m4();
}

ejecutar salida

execution(void com.javacode2018.aop.demo9.test12.S12Parent.m1())
我是S12Parent.m1()方法
我是S12.m2()方法
execution(void com.javacode2018.aop.demo9.test12.S12.m3())
我是S12.m3()方法
我是S12.m4()方法

resultados de analisis

El método m1 está ubicado en S12Parent, con la anotación @Ann12 y está conectado. El método m3 tiene la anotación @Ann12 y es interceptado. No hay anotación @Ann12 en el método m4, y no es interceptado Los resultados de ejecución de estos tres métodos son muy fáciles de entender.

El punto es que el resultado de ejecución del método m2 no ha sido interceptado. Aunque el método m2 también está marcado con la anotación @Ann12 cuando se define en S12Parent, este método es reescrito por S1. No hay anotación @Ann12 cuando se se define en S1 Código De hecho, se llama al método m2 en S1 y se encuentra que no hay una anotación @Ann12 en este método, por lo que no se intercepta.
¡Los detalles de uso de la colección para este pointcut hacen clic aquí! ! ! !

@Around("aopPoint() && @annotation(dbRouter)")
public Object doRouter(ProceedingJoinPoint jp, DBRouter dbRouter) throws Throwable {
    
    
    String dbKey = dbRouter.key();
    //StringUtils类与String类的区别在于:此类是null安全的,
    //即如果输入参数String为null,则不会抛出NullPointerException异常,代码更健壮。
    if (StringUtils.isBlank(dbKey)) throw new RuntimeException("annotation DBRouter key is null!");

    // 计算路由
    String dbKeyAttr = getAttrValue(dbKey, jp.getArgs());
    int size = dbRouterConfig.getDbCount() * dbRouterConfig.getTbCount();

    // 扰动函数
    int idx = (size - 1) & (dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16));

    // 库表索引
    int dbIdx = idx / dbRouterConfig.getTbCount() + 1;
    int tbIdx = idx - dbRouterConfig.getTbCount() * (dbIdx - 1);   

    // 设置到 ThreadLocal
    DBContextHolder.setDBKey(String.format("%02d", dbIdx));
    DBContextHolder.setTBKey(String.format("%02d", tbIdx));
    logger.info("数据库路由 method:{} dbIdx:{} tbIdx:{}", getMethod(jp).getName(), dbIdx, tbIdx);
   
    // 返回结果
    try {
    
    
        return jp.proceed();
    } finally {
    
    
        DBContextHolder.clearDBKey();
        DBContextHolder.clearTBKey();
    }
}

DBContextHolderEsta autodefinición es que la fuente de datos es de dos ThreaLocaltipos en el contexto dbKeyy tbKey
define los métodos set y get y clearDBKey()otros métodos.dbKey.remove();

  • El código de implementación de la lógica del núcleo simplificado es el anterior. Primero, extraemos el número de productos de la tabla de la biblioteca y lo usamos con la misma longitud que HashMap.
  • Luego, use la misma lógica de función de perturbación que HashMap para hacer que los datos sean más manejables.
  • Después de calcular una posición de índice en la longitud total, es necesario convertir esta posición en la tabla de la biblioteca para ver en qué biblioteca y en qué tabla cae el índice de la longitud total.
  • Finalmente, la información del índice calculado se almacena en ThreadLocal, que se usa para pasar la información del índice que se puede extraer durante la llamada al método.

5. Subtabla de procesamiento del interceptor Mybatis

Este contenido pertenece al contenido de la serie de código fuente de Mybatis mybatis: basado en la implementación de la subtabla del interceptor mybatis.

  • Al principio, considere agregar campos directamente a la tabla correspondiente a Mybatis INSERT INTO user_strategy_export_${tbIdx} para procesar las subtablas. Pero no se ve elegante, pero no descarta esta forma de uso, y aún se puede usar.
  • Luego, podemos procesar en función del interceptor Mybatis, modificar dinámicamente y agregar información de subtablas interceptando declaraciones SQL y luego configurarlo nuevamente en Mybatis para ejecutar SQL.
  • Además, mejore algunas operaciones de enrutamiento de subtablas y subbases de datos, como configurar los campos de subtablas y subbases de datos predeterminados y tomar este campo como el campo de enrutamiento predeterminado al ingresar parámetros en un solo campo.
  • Uso de la función Pattern.compile en Java
@Intercepts({
    
    @Signature(type = StatementHandler.class, method = "prepare", args = {
    
    Connection.class, Integer.class})})
public class DynamicMybatisPlugin implements Interceptor {
    
    


    private Pattern pattern = Pattern.compile("(from|into|update)[\\s]{1,}(\\w{1,})", Pattern.CASE_INSENSITIVE);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    
        // 获取StatementHandler
        //先拦截到RoutingStatementHandler,
        //里面有个StatementHandler类型的delegate变量,
        //其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
 
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

        // 获取自定义注解判断是否进行分表操作
        String id = mappedStatement.getId();
        String className = id.substring(0, id.lastIndexOf("."));
        Class<?> clazz = Class.forName(className);
        DBRouterStrategy dbRouterStrategy = clazz.getAnnotation(DBRouterStrategy.class);
        if (null == dbRouterStrategy || !dbRouterStrategy.splitTable()){
    
    
          // 传递给下一个拦截器处理
            return invocation.proceed();
        }

        // 获取SQL
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();

        // 替换SQL表名 USER 为 USER_03
        Matcher matcher = pattern.matcher(sql);
        String tableName = null;
        if (matcher.find()) {
    
    
            tableName = matcher.group().trim();
        }
        assert null != tableName;
        String replaceSql = matcher.replaceAll(tableName + "_" + DBContextHolder.getTBKey());

        // 通过反射修改SQL语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, replaceSql);

        return invocation.proceed();
    }

}

inserte la descripción de la imagen aquí

  • Implemente el método de intercepción de la interfaz Interceptor, obtenga StatementHandler, juzgue si realizar operaciones de división de tablas a través de anotaciones personalizadas, obtenga SQL y reemplace el nombre de la tabla SQL USER con USER_03, y finalmente modifique la declaración SQL a través de la reflexión.
  • Las expresiones regulares se usarán aquí para interceptar el sql coincidente, (desde|hasta|actualizar)[\s]{1,}(\w{1,})

por fin

El siguiente paso es verificar la subbase de datos y la subtabla.

  • Paquete db-router-spring-boot-starter
  • importar archivo pom
<dependency>
    <groupId>cn.bugstack.middleware</groupId>
    <artifactId>db-router-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
  1. Agregue anotaciones a los métodos DAO que necesitan usar el enrutamiento de la base de datos
    cn.itedus.lottery.infrastructure.dao.IUserTakeActivityDao
@Mapper
public interface IUserTakeActivityDao {
    
    

    /**
     * 插入用户领取活动信息
     *
     * @param userTakeActivity 入参
     */
    @DBRouter(key = "uId")
    void insert(UserTakeActivity userTakeActivity);

}

@DBRouter(key = "uId") key es un atributo en el objeto de entrada, que se usa para extraer y usar como un campo de enrutamiento de subtabla de subbase de datos

Supongo que te gusta

Origin blog.csdn.net/qq_41810415/article/details/128985777
Recomendado
Clasificación