Konstruktionsschema für Routing-Komponenten (Unterdatenbank und Untertabelle) V1

Routing-Komponenten-Konstruktionsschema V1

Realisierungseffekt: Realisieren Sie den Vorgang der Datenverteilung auf verschiedene Tabellen in verschiedenen Bibliotheken durch Anmerkungen.
Verwirklichen Sie die folgenden Hauptteile:

  1. Konfiguration und Laden der Datenquelle
  2. Dynamischer Wechsel von Datenquellen
  3. Schnittpunkteinstellung und Datenabfang
  4. Dateneinfügung

Beteiligte Wissenspunkte:

  1. Verwandte Konzepte von Unterdatenbank und Untertabelle
  2. Hash-Algorithmus
  3. Wechsel der Datenquelle
  4. AOP-Aspekt
  5. Mybatis-Abfangjäger

Konfiguration und Laden der Datenquelle

Um mehrere Datenquellen zu erhalten, müssen wir unbedingt in yamloder propertieskonfigurieren . Zuerst müssen Sie also die Konfigurationsinformationen abrufen;
die Bibliotheken und Tabellen in der Konfigurationsdatei definieren:

server:
  port: 8080
# 多数据源路由配置
router:
  jdbc:
    datasource:
      dbCount: 2
      tbCount: 4
      default: db00
      routerKey: uId
      list: db01,db02
      db00:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://xxxxx:3306/xxxx?useUnicode=true
        username: xxxx
        password: 111111
      db01:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://xxxxx:3306/xxxxx?useUnicode=true
        username: xxxxx
        password: 111111
      db02:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://xxxxx:3306/xxxx?useUnicode=true
        username: xxxxx
        password: 111111
mybatis:
  mapper-locations: classpath:/com/xbhog/mapper/*.xml
  config-location:  classpath:/config/mybatis-config.xml

Um benutzerdefinierte Datenquellenkonfigurationsinformationen zu implementieren und zu verwenden, lassen Sie SpringBoot den Speicherort zu Beginn des Startvorgangs ermitteln.
Ladereihenfolge erster Klasse: Automatische Konfiguration angeben;

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xbhog.db.router.config.DataSourceAutoConfig

Um eine derart große benutzerdefinierte Informationskonfiguration zu lesen, müssen Sie  org.springframework.context.EnvironmentAware die Schnittstelle verwenden, um die Konfigurationsdatei abzurufen und die erforderlichen Konfigurationsinformationen zu extrahieren.

public class DataSourceAutoConfig implements EnvironmentAware {

    @Override
    public void setEnvironment(Environment environment){
        ......
    }
}

Das Präfix in der Attributkonfiguration muss mit dem Attribut in der Routing-Komponente konfiguriert werden:
Was hier eingestellt wird, muss auf den entsprechenden Namen in der Konfigurationsdatei gesetzt werden

String prefix = "router.jdbc.datasource.";

Erhalten Sie die entsprechende Bibliotheksmenge dbCount, Tabellenmenge tbCountund Datenquelleninformationen entsprechend ihrem Präfix dataSource.

//库的数量
dbCount = Integer.valueOf(environment.getProperty(prefix + "dbCount"));
//表的数量
tbCount = Integer.valueOf(environment.getProperty(prefix + "tbCount"));
//分库分表数据源
String dataSources = environment.getProperty(prefix + "list");

Wenn mehrere Datenquellen vorhanden sind, verwenden Sie Folgendes Mapzum Speichern: Map<String,Map<String,Object>> daraSources;

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

dataSourceRealisieren Sie die Instanziierung der Datenquelle über die Methode: Erstellen Sie die Instanziierung basierend auf den aus den Konfigurationsinformationen gelesenen Datenquelleninformationen.
Fügen Sie die erhaltenen Informationen zur Instanziierung in DynamicDataSourcedie Klasse (übergeordnete Klasse: DataSource) ein ( setTargetDataSources, setDefaultTargetDataSource);
fügen Sie unsere benutzerdefinierte Datenquelle zur SpringContainerverwaltung hinzu.

//创建数据源
Map<Object, Object> targetDataSource = new HashMap<>();
//遍历数据源的key和value
for(String dbInfo : dataSourceMap.keySet()){
    Map<String, Object> objectMap = dataSourceMap.get(dbInfo);
    targetDataSource.put(dbInfo,new DriverManagerDataSource(objectMap.get("url").toString(),
            objectMap.get("username").toString(),objectMap.get("password").toString()));
}
//这是数据源
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSource);
//defaultDataSourceConfig的输入点
dynamicDataSource.setDefaultTargetDataSource(new DriverManagerDataSource(defaultDataSourceConfig.get("url").toString(),
        defaultDataSourceConfig.get("username").toString(),defaultDataSourceConfig.get("password").toString()));
return dynamicDataSource;

Die Vorkonfiguration hier ist im Frühjahr vollständig abgeschlossen, gefolgt vom Einfügen von Daten, also mybatisder Operation: einschließlich der zufälligen Berechnung der Bibliothekstabelle und der Implementierung des Dateninterceptors.

Datenquellen dynamisch wechseln

Die Implementierung der Routing-Umschaltung erfolgt über AbstractRoutingDataSourceeine abstrakte Klasse, die als DataSourceRouting-Vermittler fungiert und DataSourcezur Laufzeit entsprechend einem bestimmten Schlüsselwert dynamisch auf die reale Klasse umschaltet. Vererbt AbstractDataSourceund AbstractDataSourceimplementiert DataSource;
gemäß AbstractRoutingDataSourceder Methode determineTargetDataSource:

Ruft die aktuelle Zieldatenquelle ab. Bestimmt den aktuellen Suchschlüssel, targetDataSourcesführt eine Suche in der Karte durch und greift bei Bedarf auf die angegebene Standardzieldatenquelle zurück.

protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = determineCurrentLookupKey();
    DataSource dataSource = this.resolvedDataSources.get(lookupKey);
    if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
        dataSource = this.resolvedDefaultDataSource;
    }
    if (dataSource == null) {
        throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    }
    return dataSource;
}

Die Methode dient determineCurrentLookupKeyzur Ermittlung des aktuellen Suchschlüssels (Datenquellenschlüssel);

Geben Sie den von der abstrakten Methode determineCurrentLookupKey()zurückgegebenen Schlüsselwert ein DataSourceund resolvedDataSourcesentfernen Sie dann den entsprechenden Schlüssel gemäß dem Schlüssel aus der Karte DataSource. Wenn er nicht gefunden werden kann, verwenden Sie den Standardwert resolvedDefaultDataSource.

	/**
	 *确定当前查找键。这通常用于检查线程绑定的事务上下文。 
	 *允许任意键。返回的键需要匹配由resolveSpecifiedLookupKey方法解析的存储查找键类型
	 */
	@Nullable
	protected abstract Object determineCurrentLookupKey();

determineCurrentLookupKeyWir müssen also nur den Namen unserer Switch-Datenquelle umschreiben und angeben;

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return "db"+ DBContextHolder.getDBKey();
    }
}

Dieser Teil entspricht dem vorherigen Vorgang zum Erstellen einer Datenquelle, der implementiert wird , und die Standarddatenquelle ( ) und die Zieldatenquelle ( ) DynamicDataSourcewerden übergeben .setDefaultTargetDataSourcesetTargetDataSources

benutzerdefinierter Pointcut

Die Konfiguration und Informationen der vorherigen Datenquelle wurden Springim Container abgelegt und können jederzeit verwendet werden. Gemäß der Anmerkung werden die Daten in der Methode vom Interceptor abgefangen. Führen Sie die Operation der Unterbibliothek und der Untertabelle durch, berechnen Sie sie mithilfe der Störungsfunktion und speichern Sie das Ergebnis in ThreadLocal, was zum späteren Lesen praktisch ist.

Anmerkungsimplementierung:

Notizen zur Unterbibliothek: Legen Sie zunächst die drei Elemente fest.

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

    /** 分库分表字段 */
    String key() default "";
}

Durch Anpassen des Schnittpunkts @Around(**"aopPoint()&&@annotation(dbRouter)"**)wird der entsprechende Wert abgefangen, wenn die Annotation verwendet wird:
Beim Umschließen wird beurteilt, ob die Annotation in der Methode einem Wert entspricht, und wenn ja, valuewird die Routing-Berechnung anhand der übergebenen Parameter durchgeführt der Anmerkung und der Methode:
Berechnungsregeln:

  1. Rufen Sie die von der Methode übergebenen Parameter ab
  2. Berechnen Sie die Gesamtzahl der Datenbanktabellen:dbCount*tbCount
  3. Berechnen Sie idx:**int **idx = (size -1) & (Key.hashCode() ^ (Key.hashCode() >>> 16))
    1. Einfache Erklärung: Nach der UND-Verknüpfungskennung wird die Zufälligkeit durch Mischen der High- und Low-Bits erhöht
  4. **int **dbIdx = idx / dbCount() + 1
  5. **int **tbIdx = idx - tbCount() * (dbIdx - 1)

Speichern Sie die berechneten Nachteile durch die oben genannten Vorgänge in ThreadLocal.
Rufen Sie die von der Methode übergebenen Parameter ab:

private String getAttrValue(String dbKey, Object[] args) {
    if(1 == args.length){
        return args[0].toString();
    }
    String filedValue = null;
    for(Object arg : args){
        try{
            if(StringUtils.isNotBlank(filedValue)){
                break;
            }
            filedValue = BeanUtils.getProperty(arg,dbKey);
        }catch (Exception e){
            log.info("获取路由属性失败 attr:{}", dbKey,e);
        }
    }
    return filedValue;
}

benutzerdefinierter Abfangjäger

Wir haben definiert, dass der Interceptor die Vorbereitungsmethode, deren Parametertyp Verbindung ist, abfängt StatementHandler( in der SQLGrammatikkonstruktionsverarbeitungmybatis abfangen). Insbesondere müssen wir tief in den Quellcode eintauchen;
die Hauptfunktion: Abfangen, bevor SQL-Anweisungen ausgeführt werden, und SQL-Änderung implementieren für verwandte Funktionen.
Im obigen Artikel geht es hauptsächlich darum, sich auf die Untertabelle der Unterdatenbank vorzubereiten. Der nächste Schritt besteht darin, zu entscheiden, in welche Datenbank und welche Tabelle die Daten eingegeben werden sollen. Erhalten Sie über (MyBatis führt das SQL-Skriptobjekt direkt in der
Datenbank StatementHandleraus ) mappedStatement(MappedStatement verwaltet eine Knotenkapselung <select|update|delete|insert>) und beurteilt anhand maperdStatementder erhaltenen benutzerdefinierten Annotation dbRouterStrategy, ob eine Tabellenteilungsoperation durchgeführt werden soll.

Class<?> clazz = Class.forName(className);
DBRouterStrategy dbRouterStrategy = clazz.getAnnotation(DBRouterStrategy.class);
if (null == dbRouterStrategy || !dbRouterStrategy.splitTable()){
    return invocation.proceed();
}

dbRouterStrategyBeachten Sie, dass die Tabelle standardmäßig falsenicht geteilt wird, sondern die Daten direkt eingefügt [aktualisiert] werden.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface DBRouterStrategy {
    boolean splitTable() default false;
}

Wenn die Untertabellenanmerkung vorhanden ist oder der Untertabellenparameter lautet true, führen Sie die folgenden vier Schritte aus:

  1. get-sql

    BoundSql: Zeigt dynamisch generierte SQLAnweisungen und entsprechende Parameterinformationen an.

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

Teilen Sie [Einfügen/Auswählen/Aktualisieren] und den Tabellennamen durch regelmäßigen Abgleich auf, um das Zusammenfügen nachfolgender Tabellennamen zu erleichtern.

//替换SQL表名USER为USER_3;
Matcher matcher = pattern.matcher(sql);
String tableName = null;
if(matcher.find()){
    tableName = matcher.group().trim();
}
  1. SQL verbinden

Die Anweisung wird durch Reflection geändert SQLund der Tabellenname wird ersetzt. filed.set()Das durch dieses Feldobjekt dargestellte Feld für das angegebene Objektargument wird auf den angegebenen neuen Wert gesetzt. Entpacken Sie neue Werte automatisch, wenn das zugrunde liegende Feld einen primitiven Typ hat

assert null != tableName;
String replaceSQL = matcher.replaceAll(tableName + "_" + DBContextHolder.getTBKey());
//通过反射修改SQL语句
Field filed = boundSql.getClass().getDeclaredField("sql");
filed.setAccessible(true);
filed.set(boundSql,replaceSQL);

Referenzartikel

Java-Anmerkungen – dynamische Datenquellenumschaltung realisieren – aheizi – 博客园

Zusammenfassung der Verwendung von MyBatis-Abfangjägern

Interpretation der Blog-CSDN-Funktion blog_disturbance von HashMap-Störung function_supercmd

Warum stört HashMap die Funktion? |Java-Bürstenfragen und Lochkarten – Nuggets

Autor dieses Artikels: xbhog

Link zu diesem Artikel: https://www.cnblogs.com/xbhog/p/16808738.html

Je suppose que tu aimes

Origine blog.csdn.net/weixin_47367099/article/details/127458638
conseillé
Classement