カスタム サブライブラリおよびサブテーブル コンポーネント (サブライブラリおよびサブテーブルを実装するコンポーネント) - Java

シリーズ記事ディレクトリ


序文

まず、上記の基本的な知識を確認してください -なぜサブデータベースサブテーブルなのか

この記事は、Fu 兄弟のメモを整理した私自身の学習メモに基づいています。学習のみを目的としています。侵害がある場合は、ご連絡ください。

この記事は、サブデータベースおよびサブテーブルコンポーネントの具体的な実装です。図に示すように、私たちが達成したいのは、水平分割のルーティング設計でもあります。
ここに画像の説明を挿入

1. 必要な技術

  • サブデータベースおよびサブテーブル ロジックの処理を容易にするために、データベース ルーティングを使用してメソッドをマークする必要があるため、AOP アスペクト インターセプトの使用について説明します。
  • データ ソースの切り替え操作には、サブデータベースがあるため、データを異なるデータベースに割り当てるために複数のデータ ソース間のリンク切り替えが含まれます。
  • データがどのデータベースのどのテーブルに割り当てられるかというデータベース テーブルのアドレス指定操作には、インデックスの計算が必要です。メソッド呼び出しの過程で、最終的に ThreadLocal によって記録されます。
  • データを複数のテーブルに均等に分散させるためには、データベースをテーブルに分割した後、あるデータベースの特定のテーブルにデータを集中させるため、データのハッシュ演算をどのように実行するかを考慮する必要があります。データが失われること サブデータベースサブテーブルの重要性。
    まとめると、データの保存はデータベースとテーブルのデータ構造で完結していることがわかり、使用する必要がある技術は以下のとおりですAOP、数据源切换、散列算法、哈希寻址、ThreadLocal以及SpringBoot的Starter开发方式そして、ハッシュ、アドレス指定、データストレージと同様に、実際、そのようなテクノロジーは HashMap と非常に多くの類似点があります。

2. 技術概要

1.スレッドローカル

ここに画像の説明を挿入

@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.ハッシュマップ

ここに画像の説明を挿入

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

3、悟る

1. ルーティングのアノテーションを定義する

カスタム注釈

  • 概念: 手順を説明します。コンピュータに関する
    注意事項: プログラムは言葉で説明してください。プログラマー向けの定義
    : アノテーション。メタデータとも呼ばれます。コードレベルの仕様。JDK1.5以降で導入された機能で、クラス、インターフェース、列挙型と同レベルです。パッケージ、クラス、フィールド、メソッド、ローカル変数、メソッドパラメータなどの前で宣言して、これらの要素について説明したりコメントしたりすることができます。概念の説明: JDK1.5 以降の新機能は
    プログラムの使用方法を説明します アノテーション: @ アノテーション名

フォーマット

メタアノテーション public @interface アノテーション名 { プロパティリスト; }

アノテーションは本質的にはインターフェースであり、デフォルトでアノテーション・インターフェースを継承します。

public interface MyAnno extends java.lang.annotation.Annotation {}
インターフェイスには抽象メソッドが含まれる場合があります

必須

1. 属性の戻り値の型には、基本データ型、文字列、列挙型、および上記の種類のアノテーションの配列の値があります 2. 属性を定義した後、使用するときに属性に値を割り当てる必要があり
ます
3. 属性を定義するときは、defaultキーワードを使用してプロパティのデフォルトの初期値を付与しますが、アノテーションを使用する場合は、プロパティを割り当てる必要はありません。
4. 割り当てる必要がある属性が 1 つだけで、その属性の名前が value である場合、値を省略して、値を直接定義できます。
配列を割り当てる場合、値は {} で囲まれます。配列内の値が 1 つだけの場合は、{} を省略できます

定义:
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 {
    
    

}

メタアノテーション: アノテーションを説明するために使用されるアノテーション

@Target:記述アノテーションが作用できる位置の値
ElementType
TYPE:クラスに作用できる
METHOD:メソッドに作用できる
FIELD:メンバ変数に作用できる :
@Retentionアノテーションが保持されるステージを記述する
@Retention(RetentionPolicy.RUNTIME):現在記述されているアノテーションが保持されるクラスのバイトコード ファイル内および JVM によって読み取られるカスタム アノテーションは通常、これを使用します。
@Documented: 説明アノテーションが API ドキュメントに抽出されるかどうか
@Inherited: 説明アノテーションがサブクラスに継承されるかどうか

プログラム内でアノテーションを使用(解析)する:アノテーションに定義されている属性値を取得する

これまでの反省の

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

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

リフレクションでは、設定ファイルを読み込むことで任意のクラスのオブジェクトを作成し、任意のメソッドを実行できます。
構成ファイルの読み取りに関連する上記の操作を、アノテーションを介して置き換えることができます。具体的なコードは次のとおりです。 注釈は次のように定義されます。

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

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

    String className();
    String methodName();
}

アノテーションの設定を解析することで、関連するオブジェクトの作成やオブジェクトのメソッドの実行を行います。

  • アノテーションで定義された位置のオブジェクト(クラス、メソッド、フィールド)を取得します
  • 指定されたアノテーションを取得する
  • アノテーション内の抽象メソッドを呼び出して、設定された属性値を取得します。
    コードは次のとおりです。
@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);
    }
}

小さな例: アノテーションは単純なテスト フレームワークを定義します

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

電卓ツールクラスを定義し、メソッドで @Check アノテーションを使用します。

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

}

テスト フレームワーク クラスを定義してテストを実行し、テスト例外を bug.txt ファイルに記録します。コードは次のとおりです。

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

    }

}

テストの実行後、次のように src と同じレベルのディレクトリにある bug.txt ファイルの内容を表示できます。

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

実践的なライティング

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

    String key() default "";

}

小さな要約

ほとんどの場合、カスタム アノテーションではなく、アノテーションのみを使用します。
注釈は誰のためのものですか?

  • 翻訳者
  • プログラムを解析するための注釈の使用は
    プログラムの一部ではありません。注釈はラベルであると理解できます。
@Mapper
public interface IUserDao {
    
    

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

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

}

  • まず、データベースによってルーティングされる必要があるメソッドに配置されるアノテーションをカスタマイズする必要があります。
  • 利用方法はメソッドによるアノテーションの設定であり、当社指定のAOPアスペクトによりインターセプト可能であり、インターセプト後、対応するデータベースルーティング計算と判定が行われ、対応する運用データソースが切り替えられます。

2. ルーティング設定の解析

この記事もよく書かれています
ここをクリックしてください!

ルーティング構成の場合、独自の application.yml で複数のデータ ソース構成を定義するためにサブデータベースとサブテーブルをセットアップする必要があります。

3つのライブラリの情報を設定する

  • 上記はデータベースルーティングコンポーネント実装後のデータソース構成ですが、サブデータベースやサブテーブル配下のデータソースを利用する場合には、データソースの拡張に対応するために複数のデータソースの情報構成をサポートする必要があります。さまざまなニーズの。
  • この大規模なカスタム情報構成の場合、org.springframework.context.EnvironmentAwareインターフェース。

構成を取得するには、上記のインターフェイスを実装してsetEnvironmentメソッドを書き直す必要があります。

ここでは、Spring によって管理されるすべてのクラスを紹介します。これらのクラスは、EnvironmentAware インターフェイスを実装し、プロジェクトの開始時にシステム環境変数とアプリケーション構成ファイル内の変数を取得するために setEnvironmentメソッドを書き換えます。
:

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

application.properties ファイルの情報は次のとおりです。

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

もちろん、これはPropertyUtil独自に定義した読み込み設定ファイル操作ツールクラスです。ハンドル関数(スプリングブート版による)は、ツールクラスへのリフレクションにより、独自の v1 および v2 メソッドにジャンプします。上記のカスタムの構成解析と同様です。注釈

3. データソースの切り替え

SpringBoot と連携して開発された Starter では、DataSource のインスタンス化されたオブジェクトを提供する必要があり、このオブジェクトを DataSourceAutoConfig に配置して実装します。ここで提供されるデータ ソースは動的に変更できます。つまり、データの動的な切り替えをサポートします。ソース。
ここで説明してください

2 つのアノテーションの役割

Spring Boot では、Java 構成を使用して XML 構成を完全に置き換えることを推奨しています。Java 構成は、@Configration および @Bean アノテーションによって実装されます。どちらも次のように機能します。

  • @Configration アノテーション: 現在のクラスが設定クラスであることを宣言します。これは Spring の XML ファイルに相当します。
  • @Bean アノテーション: メソッドに作用し、現在のメソッドの戻り値が Bean であることを宣言します。
    理解できない場合は、ここをクリック。

同時に、@Component と @Bean の違いについても楽観的に考える必要があります。

データソースの作成

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

ここでは簡略化して作成する場合を示します。構成情報から読み取ったデータソース情報を元に、インスタンス化を作成します。
データベースが複数あるため、DynamicDataSource が使用され
、このライブラリDriverManagerDataSource は
データベースに接続するための単なる手段です。

データ ソースが作成されると、データ ソースはDynamicDataSourceに。DynamicDataSource クラスは、この記事のカスタム クラスAbstractRoutingDataSourceです。これは から継承された実装クラスです。このクラスは、特定の呼び出しに対応するデータ ソース情報を保存および読み取りできます。
参考記事:こちら!
targetDataSources: 複数のデータ ソースを保存するマップです
。defaultTargetDataSource: デフォルトのデータ ソースを参照します
。以下は良い記事です。まず、targetDataSources は、キーに従って異なるデータ ソースを保存するマップです。ソース コードで確認できます。 targetDataSources は、別のマップ変数、resolvedDataSources に変換され、defaultTargetDataSource は、resolvedDefaultDataSource に変換されます。

Springboot の動的マルチデータ ソースの構成と使用 (2)

4. セクションインターセプト

AOP のアスペクト インターセプト完了する必要があります。データベース ルーティングの計算、摂動関数の拡張ハッシュ、ライブラリ テーブル インデックスの計算、データ ソースを転送するための ThreadLocal への設定、全体的なケース コードは次のとおりです。
Pointcut エントリ ポイント ギャザーを追加しました

@annotation を Pointcut に追加: 現在の実行メソッドが指定されたアノテーションを保持しているメソッドと一致するために使用されます。

@annotation (注釈タイプ): 呼び出されたメソッドの指定された注釈と一致します。

ケース

メソッドで使用できるアノテーションを定義する

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

2つのクラスを定義する

S12Parent は親クラスで、内部的に 2 つのメソッドを定義します。どちらのメソッドにも @Ann12 アノテーションが付いています。S12 は
エージェントのターゲット クラスであり、S12Parent のサブクラスです。m2 メソッドは内部で書き換えられます。書き換え後、m2 には @ はありませんAnn12 アノテーション、S12 では 2 つのメソッド m3 と m4 も定義されており、m3 には @Ann12 アノテーションがあります

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

アスペクトクラスに来てください

呼び出されたターゲット メソッドに @Ann12 アノテーションが付けられている場合、beforeAdvice によって処理されます。
パッケージ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);
    }
}

テストケース

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

実行出力

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

分析結果

m1 メソッドは S12Parent にあり、@Ann12 アノテーションがあり、接続されています。m3 メソッドは @Ann12 アノテーションがあり、インターセプトされます。m4 メソッドには @Ann12 アノテーションがありません。これら 3 つのメソッドの実行結果は非常に簡単です。

ポイントはm2メソッドの実行結果をインターセプトしない点です。m2メソッドもS12Parentで定義する際には@Ann12アノテーションが付けられますが、このメソッドはS1によって書き換えられます。実際には、S1 の m2 メソッドが呼び出されますが、このメソッドには @Ann12 アノテーションがないことがわかり、インターセプトされません。
このポイントカットのコレクション使用法の詳細はここをクリックしてください!

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

DBContextHolderこの自己定義とは、コンテキストにおいてデータソースが2ThreaLocal種類ありdbKeytbKey
setメソッドやgetメソッドclearDBKey()などのメソッドを定義するものです。dbKey.remove();

  • 簡略化したコアロジックの実装コードは上記の通りですが、まずライブラリテーブルの積数を抽出し、HashMapと同じ長さとして使用します。
  • 次に、HashMap と同じ摂動関数ロジックを使用して、データをよりハッシュ可能にします。
  • 全長のインデックス位置を計算した後、その位置をライブラリテーブルに変換して、全長のインデックスがどのライブラリのどのテーブルに該当するかを確認する必要があります。
  • 最後に、計算されたインデックス情報は ThreadLocal に保存されます。これは、メソッド呼び出し中に抽出できるインデックス情報を渡すために使用されます。

5. Mybatis インターセプター処理サブテーブル

このコンテンツは、mybatis ソース コード シリーズmybatis: のコンテンツに属しており、mybatis インターセプタ サブテーブル実装に基づいています。

  • 最初に、Mybatis INSERT INTO user_strategy_export_${tbIdx} に対応するテーブルにフィールドを直接追加して、サブテーブルを処理することを検討してください。見た目はエレガントではありませんが、この使用方法を排除するものではなく、まだ使用できます。
  • 次に、Mybatis インターセプターに基づいて処理し、SQL ステートメントをインターセプトすることでサブテーブル情報を動的に変更および追加し、Mybatis に戻して SQL を実行します。
  • さらに、デフォルトのサブデータベースおよびサブテーブルのフィールドを構成し、単一フィールドにパラメータを入力するときにデフォルトでこのフィールドをルーティング フィールドとして取得するなど、一部のサブデータベースおよびサブテーブルのルーティング操作が改善されました。
  • Java でのPattern.compile 関数の使用法
@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();
    }

}

ここに画像の説明を挿入

  • Interceptor インターフェースの intercept メソッドを実装し、StatementHandler を取得し、カスタム アノテーションによってテーブル分割操作を実行するかどうかを判断し、SQL を取得して SQL テーブル名 USER を USER_03 に置き換え、最後にリフレクションを通じて SQL ステートメントを変更します。
  • ここでは正規表現を使用して、一致する SQL、(from|into|update)[\s]{1,}(\w{1,}) をインターセプトします。

やっと

次のステップでは、サブデータベースとサブテーブルを確認します。

  • パッケージ db-router-spring-boot-starter
  • pomファイルをインポートする
<dependency>
    <groupId>cn.bugstack.middleware</groupId>
    <artifactId>db-router-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
  1. データベース ルーティングを使用する必要がある DAO メソッドにアノテーションを追加する
    cn.itedus.lottery.infrastructure.dao.IUserTakeActivityDao
@Mapper
public interface IUserTakeActivityDao {
    
    

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

}

@DBRouter(key = "uId") キーは入力オブジェクトの属性であり、サブデータベースのサブテーブルのルーティング フィールドを抽出して使用するために使用されます。

おすすめ

転載: blog.csdn.net/qq_41810415/article/details/128985777