Custom sub-library and sub-table components (components that implement sub-library and sub-table)——java

Series Article Directory


foreword

You can first look at the basic knowledge above- why sub-database sub-table

This article is based on my own study notes organized by Brother Fu's notes. It is only for learning. If there is any infringement, please contact

This article is the specific implementation of sub-database and sub-table components: what we want to achieve is also the routing design of horizontal split, as shown in the figure
insert image description here

1. Required technology

  • It is about the use of AOP aspect interception, because it is necessary to mark the method using database routing to facilitate the processing of sub-database and sub-table logic.
  • The data source switching operation, since there are sub-databases, will involve link switching between multiple data sources in order to assign data to different databases.
  • The database table addressing operation, which database and which table a piece of data is assigned to, requires index calculation. In the process of method calls, it is finally recorded by ThreadLocal.
  • In order to allow data to be evenly distributed to different database tables, it is also necessary to consider how to perform data hash operations. After the database is divided into tables, let the data be concentrated in a certain table in a certain database, so that the data will be lost. The significance of sub-database sub-table.
    To sum up, it can be seen that the data storage is completed under the data structure of the database and the table. The technologies I need to use include: AOP、数据源切换、散列算法、哈希寻址、ThreadLocal以及SpringBoot的Starter开发方式and other technologies. And like hashing, addressing, and data storage, in fact, such technologies have too many similarities with HashMap

2. Technical summary

1. ThreadLocal

insert image description here

@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.HashMap

insert image description here

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

Three, realize

1. Define routing annotations

custom annotation

  • Concept : Describes the procedure.
    Notes for computers : Describe programs in words. Definition for programmers
    : Annotation, also called metadata. A code-level specification. It is a feature introduced by JDK1.5 and later versions, and it is at the same level as classes, interfaces, and enumerations. It can be declared in front of packages, classes, fields, methods, local variables, method parameters, etc., to explain and comment on these elements. Concept description: The new features after JDK1.5 explain
    the use of the program Annotation: @ annotation name

Format

meta-annotation public @interface annotation-name { property-list; }

An annotation is essentially an interface, which inherits the Annotation interface by default

public interface MyAnno extends java.lang.annotation.Annotation {}
Interfaces may have abstract methods

Require

1. The return value type of the attribute has the following values: basic data type, String, enumeration, and array of the above types of annotations. 2. After defining the attribute, you
need to assign a value to the attribute when using
it. 3. When defining the attribute, use defaultkeywords to give The default initialization value of the property, when using the annotation, it is not necessary to assign the property.
4. If only one attribute needs to be assigned, and the name of the attribute is value, the value can be omitted, and the value can be defined directly.
When assigning an array, the value is wrapped with {}. {} can be omitted if there is only one value in the array

定义:
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-annotations: Annotations used to describe annotations

@Target: Describe the position where the annotation can act
ElementTypeValue:
TYPE: Can act on the class
METHOD: Can act on the method
FIELD: Can act on the member variable:
@RetentionDescribe the stage where the annotation is retained
@Retention(RetentionPolicy.RUNTIME): The currently described annotation will be retained in the class bytecode In the file and read by the JVM, custom annotations generally use this.
@Documented: Whether the description annotation is extracted into the api document
@Inherited: whether the description annotation is inherited by subclasses

Use (parse) annotations in the program: get the attribute values ​​​​defined in the annotations

Examples of previous reflections

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

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

In reflection, objects of any class can be created by reading configuration files, and arbitrary methods can be executed.
We can replace the above operations related to reading configuration files through annotations. The specific code is as follows: Annotations are defined as follows:

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

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

    String className();
    String methodName();
}

By parsing annotation configuration, perform related object creation and execute object methods.

  • Get the object of the location defined by the annotation (Class, Method, Field)
  • Get the specified annotation
  • Call the abstract method in the annotation to get the configured attribute value.
    The code is as follows:
@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);
    }
}

Small example: annotations define a simple test framework

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

Define a calculator tool class and use the @Check annotation on the method

/**
 * 定义的计算器类
 * @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...");
    }

}

Define the test framework class and execute the test, and record the test exception in the bug.txt file. The code is as follows:

/**
 * 简单的测试框架
 * 当主方法执行后,会自动自行被检测的所有方法(加了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();

    }

}

After executing the test, you can view the content of the bug.txt file in the same level directory as src as follows:

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

Practical writing

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

    String key() default "";

}

small summary

Most of the time, we just use annotations, not custom annotations.
Who are the annotations for?

  • translater
  • Using annotations for parsing programs
    is not part of the program, it can be understood that annotations are a label.
@Mapper
public interface IUserDao {
    
    

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

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

}

  • First of all, we need to customize an annotation to be placed on the method that needs to be routed by the database.
  • Its use method is to configure annotations through methods, and it can be intercepted by the AOP aspect we specify. After interception, the corresponding database routing calculation and judgment are performed, and the corresponding operation data source is switched.

2. Parsing routing configuration

This article is also well written
click here! ! ! ! !

For routing configuration, you need to set up sub-databases and sub-tables to define multiple data source configurations in your own application.yml

Configure the information of the three libraries

  • The above is a data source configuration after we have implemented the database routing component. In the use of data sources under sub-database and sub-table, it is necessary to support the information configuration of multiple data sources, so as to meet the expansion of different needs.
  • For this large custom information configuration, you need to use org.springframework.context.EnvironmentAwarethe interface to obtain the configuration file and extract the required configuration information.

To get the configuration, you need to implement the above interface and then rewrite setEnvironmentthe method

Here we introduce all classes managed by spring, which implement the interface EnvironmentAware and rewrite the method setEnvironment to obtain system environment variables and variables in the application configuration file when the project starts.
Example :

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

The application.properties file information is:

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

Of course, this PropertyUtilis the self-defined reading configuration file operation tool class. The handle function (according to the springboot version) jumps to its own v1 and v2 methods through reflection in the tool class. Similar to the configuration analysis of the above custom annotations

3. Data source switching

In the Starter developed in conjunction with SpringBoot, it is necessary to provide an instantiated object of DataSource, then we put this object in DataSourceAutoConfig to implement, and the data source provided here can be dynamically changed, that is, it supports dynamic switching of data sources.
explain here

The role of the two annotations

Spring Boot recommends using java configuration to completely replace XML configuration. Java configuration is implemented through @Configration and @Bean annotations. Both function as follows:

  • @Configration annotation: Declare that the current class is a configuration class, which is equivalent to an XML file in Spring
  • @Bean annotation: It acts on the method and declares that the return value of the current method is a Bean. If
    you don’t understand it, please click here ! ! ! ! ! ! !

At the same time, we should also be optimistic about the difference between @Component and @Bean

Data source creation

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

Here is a simplified creation case, based on the data source information read from the configuration information, the instantiation is created.
Since there are more than one database, DynamicDataSource is used
and this library DriverManagerDataSource
is just a way to connect to the database

After the data source is created, it is stored DynamicDataSourcein . The DynamicDataSource class is a custom class in this article AbstractRoutingDataSource. It is an implementation class inherited from . This class can store and read the corresponding data source information for specific calls.
Reference article: here! ! ! !
targetDataSources: a map that saves multiple data sources.
defaultTargetDataSource: refers to the default data source
. The following is a good article. First, targetDataSources is a map that saves different data sources according to the key. You can see in the source code that targetDataSources will be converted into another map variable, resolvedDataSources. And defaultTargetDataSource is converted to resolvedDefaultDataSource

Springboot dynamic multi-data source configuration and use (2)

4. Section interception

It needs to be completed in the aspect interception of AOP ; database routing calculation, perturbation function enhanced hash, calculation of library table index, setting to ThreadLocal to transfer data source, the overall case code is as follows: the code of the beginning aspect is added with
a Pointcut entry point gather

Add @annotation to Pointcut: used to match the method that the current execution method holds the specified annotation

@annotation (annotation type): Matches the specified annotation on the called method.

the case

Define an annotation that can be used on a method

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 {
    
    
}

define 2 classes

S12Parent is the parent class, which defines 2 methods internally. Both methods have @Ann12 annotation.
S12 is the target class of the agent and a subclass of S12Parent. The m2 method is rewritten internally. After rewriting, there is no @ on the m2 method. Ann12 annotation, S12 also defines two methods m3 and m4, and m3 has annotation @Ann12
package 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()方法");
    }
}

Come to an Aspect class

When the called target method is annotated with @Ann12, it will be processed by beforeAdvice.
package 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);
    }
}

test case

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

run output

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()方法

analysis results

The m1 method is located in S12Parent, with the @Ann12 annotation on it, and it is connected. The m3 method has the @Ann12 annotation on it, and it is intercepted. There is no @Ann12 annotation on the m4 method, and it is not intercepted. The execution results of these three methods are very easy. understand.

The point is that the execution result of the m2 method has not been intercepted. Although the m2 method is also marked with the @Ann12 annotation when it is defined in S12Parent, this method is rewritten by S1. There is no @Ann12 annotation when it is defined in S1. Code In fact, the m2 method in S1 is called, and it is found that there is no @Ann12 annotation on this method, so it is not intercepted.
Collection usage details for this pointcut click here! ! ! !

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

DBContextHolderThis self-definition is that the data source is of two ThreaLocaltypes in the context dbKeyand tbKey
defines the set and get methods and clearDBKey()other methods.dbKey.remove();

  • The simplified core logic implementation code is as above. First, we extract the number of library table products and use it as the same length as HashMap.
  • Next, use the same perturbation function logic as HashMap to make the data more hashable.
  • After calculating an index position on the total length, it is necessary to convert this position into the library table to see which library and which table the index of the overall length falls into.
  • Finally, the calculated index information is stored in ThreadLocal, which is used to pass the index information that can be extracted during the method call.

5. Mybatis interceptor processing sub-table

This piece of content belongs to the content of the Mybatis source code series mybatis: based on the mybatis interceptor sub-table implementation.

  • At the beginning, consider adding fields directly to the table corresponding to Mybatis INSERT INTO user_strategy_export_${tbIdx} to process sub-tables. But it doesn't look elegant, but it doesn't rule out this way of use, and it can still be used.
  • Then we can process based on the Mybatis interceptor, dynamically modify and add sub-table information by intercepting SQL statements, and then set it back to Mybatis to execute SQL.
  • In addition, improve some sub-database and sub-table routing operations, such as configuring the default sub-database and sub-table fields and taking this field as the routing field by default when entering parameters in a single field.
  • Usage of Pattern.compile function in 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();
    }

}

insert image description here

  • Implement the intercept method of the Interceptor interface, obtain StatementHandler, judge whether to perform table splitting operations through custom annotations, obtain SQL and replace the SQL table name USER with USER_03, and finally modify the SQL statement through reflection
  • Regular expressions will be used here to intercept matching sql, (from|into|update)[\s]{1,}(\w{1,})

at last

The next step is to verify the sub-database and sub-table

  • Package db-router-spring-boot-starter
  • import pom file
<dependency>
    <groupId>cn.bugstack.middleware</groupId>
    <artifactId>db-router-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
  1. Add annotations to DAO methods that need to use database routing
    cn.itedus.lottery.infrastructure.dao.IUserTakeActivityDao
@Mapper
public interface IUserTakeActivityDao {
    
    

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

}

@DBRouter(key = "uId") key is an attribute in the input object, which is used to extract and use as a sub-database sub-table routing field

Guess you like

Origin blog.csdn.net/qq_41810415/article/details/128985777