Heim  >  Artikel  >  Java  >  Wie SpringBoot das dynamische Umschalten mehrerer Datenquellen basierend auf AbstractRoutingDataSource implementiert

Wie SpringBoot das dynamische Umschalten mehrerer Datenquellen basierend auf AbstractRoutingDataSource implementiert

WBOY
WBOYnach vorne
2023-05-31 10:43:521120Durchsuche

1. Szenario

Im Produktionsgeschäft führen einige Aufgaben Langzeitabfragevorgänge aus. Wenn die Echtzeitanforderungen nicht hoch sind, hoffen wir, diese Abfrage-SQLs zu trennen und zur Slave-Datenbank zu wechseln abzufragen, um den Anwendungsdruck auf die Hauptdatenbank zu verringern.

Eine Lösung besteht darin, mehrere Datenquellen in der Konfigurationsdatei zu konfigurieren und dann die Datenquelle und die Mapper-bezogene Scankonfiguration über die Konfigurationsklasse abzurufen. Verschiedene Datenquellen konfigurieren unterschiedliche Mapper-Scanpositionen und dann Fügen Sie die von Ihnen benötigte Datenquelle in die Mapper-Schnittstelle ein. Diese Methode ist relativ einfach. Die Funktion besteht darin, Datenquellen durch Mapper-Scan-Standorte zu unterscheiden.

Eine praktikable Lösung besteht darin, im Voraus eine Standarddatenquelle festzulegen, mehrere andere Datenquellen gleichzeitig zu definieren und aop zum Implementieren von Anmerkungen zum Wechseln der Datenquellen zu verwenden. Das Erben der AbstractRoutingDataSource-Klasse ist der Schlüssel zur Implementierung dieser Lösung. Dies ist der Schwerpunkt dieses Artikels.

2. Prinzip

Die Kernlogik des dynamischen Umschaltens mehrerer Datenquellen von AbstractRoutingDataSource lautet: Wenn das Programm ausgeführt wird, wird die Datenquelle über AbstractRoutingDataSource dynamisch in das Programm eingebunden . Datenquellen flexibel wechseln.
Durch das dynamische Umschalten mehrerer Datenquellen basierend auf AbstractRoutingDataSource kann eine Trennung von Lesen und Schreiben erreicht werden. Die Logik lautet wie folgt:

/**
     * Retrieve the current target DataSource. Determines the
     * {@link #determineCurrentLookupKey() current lookup key}, performs
     * a lookup in the {@link #setTargetDataSources targetDataSources} map,
     * falls back to the specified
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
     * @see #determineCurrentLookupKey()
     */
    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;
    }
/**
     * Determine the current lookup key. This will typically be
     * implemented to check a thread-bound transaction context.
     * <p>Allows for arbitrary keys. The returned key needs
     * to match the stored lookup key type, as resolved by the
     * {@link #resolveSpecifiedLookupKey} method.
     */
    @Nullable
    protected abstract Object determineCurrentLookupKey();

Geben Sie die Datenquelle an, die umgeschaltet werden muss, indem Sie die abstrakte Methode discoverCurrentLookupKey

3 implementieren. Codebeispiel

Das Beispiel basiert hauptsächlich auf #🎜🎜 #

com.alibaba.druid;tk.mybatis

Definieren Sie eine Klasse zum Zuordnen von Datenquellen. Verwenden Sie TheadLocal, um das Flag (den Schlüssel) zu speichern, welche Datenquelle jeder Thread auswählt Slave Datenbank-Slave (weggelassen). Die Konfiguration der Datenquelle kann vereinfacht werden.

@Slf4j
public class DynamicDataSourceContextHolder {
 
 
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static List<String> dataSourceIds = new ArrayList<String>();
 
    public static void setDataSourceType(String dataSourceType) {
        log.info("设置当前数据源为{}",dataSourceType);
        contextHolder.set(dataSourceType);
    }
 
    public static String getDataSourceType() {
        return contextHolder.get() ;
    }
 
    public static void clearDataSourceType() {
        contextHolder.remove();
    }
 
    public static boolean containsDataSource(String dataSourceId){
        log.info("list = {},dataId={}", JSON.toJSON(dataSourceIds),dataSourceId);
        return dataSourceIds.contains(dataSourceId);
    }
}

Achten Sie auf den Bean-Namen der Master-Slave-Datenbank. 🎜🎜#targetDataSources speichert Daten. Das k-v-Paar der Quelle.

defaultTargetDataSource speichert die Standarddatenquelle. Konfigurieren des Transaktionsmanagers und der SqlSessionFactoryBean#🎜 🎜 #
public class DynamicDataSource  extends AbstractRoutingDataSource {
 
    @Override
    protected Object determineCurrentLookupKey() {
 
        return  DynamicDataSourceContextHolder.getDataSourceType();
    }
}

Definieren Sie eine Annotation, um die Geschäftslogik der Datenquelle

@Configuration
@tk.mybatis.spring.annotation.MapperScan(value = {"com.server.dal.dao"})
@ConditionalOnProperty(name = "java.druid.datasource.master.url")
public class JavaDruidDataSourceConfiguration {
 
    private static final Logger logger = LoggerFactory.getLogger(JavaDruidDataSourceConfiguration.class);
 
    @Resource
    private JavaDruidDataSourceProperties druidDataSourceProperties;
    @Primary
    @Bean(name = "masterDataSource", initMethod = "init", destroyMethod = "close")
    @ConditionalOnMissingBean(name = "masterDataSource")
    public DruidDataSource javaReadDruidDataSource() {
 
        DruidDataSource result = new DruidDataSource();
 
        try {
//            result.setName(druidDataSourceProperties.getName());
            result.setUrl(druidDataSourceProperties.getUrl());
            result.setUsername(druidDataSourceProperties.getUsername());
            result.setPassword(druidDataSourceProperties.getPassword());
            result.setConnectionProperties(
                    "config.decrypt=false;config.decrypt.key=" + druidDataSourceProperties.getPwdPublicKey());
            result.setFilters("config");
            result.setMaxActive(druidDataSourceProperties.getMaxActive());
            result.setInitialSize(druidDataSourceProperties.getInitialSize());
            result.setMaxWait(druidDataSourceProperties.getMaxWait());
            result.setMinIdle(druidDataSourceProperties.getMinIdle());
            result.setTimeBetweenEvictionRunsMillis(druidDataSourceProperties.getTimeBetweenEvictionRunsMillis());
            result.setMinEvictableIdleTimeMillis(druidDataSourceProperties.getMinEvictableIdleTimeMillis());
            result.setValidationQuery(druidDataSourceProperties.getValidationQuery());
            result.setTestWhileIdle(druidDataSourceProperties.isTestWhileIdle());
            result.setTestOnBorrow(druidDataSourceProperties.isTestOnBorrow());
            result.setTestOnReturn(druidDataSourceProperties.isTestOnReturn());
            result.setPoolPreparedStatements(druidDataSourceProperties.isPoolPreparedStatements());
            result.setMaxOpenPreparedStatements(druidDataSourceProperties.getMaxOpenPreparedStatements());
 
            if (druidDataSourceProperties.isEnableMonitor()) {
                StatFilter filter = new StatFilter();
                filter.setLogSlowSql(druidDataSourceProperties.isLogSlowSql());
                filter.setMergeSql(druidDataSourceProperties.isMergeSql());
                filter.setSlowSqlMillis(druidDataSourceProperties.getSlowSqlMillis());
                List<Filter> list = new ArrayList<>();
                list.add(filter);
                result.setProxyFilters(list);
            }
 
        } catch (Exception e) {
 
            logger.error("数据源加载失败:", e);
 
        } finally {
            result.close();
        }
 
 
        return result;
    }
}

Aspekt anzugeben. Achten Sie darauf, die Reihenfolge anzugeben, um die Ausführung sicherzustellen, bevor Sie mit der Transaktion beginnen.

@Configuration
public class DynamicDataSourceConfig {
    private static final String MAPPER_LOCATION = "classpath*:sqlmap/dao/*Mapper.xml";
 
    @Bean(name = "dynamicDataSource")
    public DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DruidDataSource masterDataSource,
                                               @Qualifier("slaveDataSource") DruidDataSource slaveDataSource) {
        Map<Object, Object> targetDataSource = new HashMap<>();
        DynamicDataSourceContextHolder.dataSourceIds.add("masterDataSource");
        targetDataSource.put("masterDataSource", masterDataSource);
        DynamicDataSourceContextHolder.dataSourceIds.add("slaveDataSource");
        targetDataSource.put("slaveDataSource", slaveDataSource);
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSource);
        dataSource.setDefaultTargetDataSource(masterDataSource);
        return dataSource;
    }
 
    @Primary
    @Bean(name = "javaTransactionManager")
    @ConditionalOnMissingBean(name = "javaTransactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DynamicDataSource druidDataSource) {
        return new DataSourceTransactionManager(druidDataSource);
    }
 
    @Bean(name = "sqlSessionFactoryBean")
    public SqlSessionFactoryBean myGetSqlSessionFactory(@Qualifier("dynamicDataSource") DynamicDataSource  dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            sqlSessionFactoryBean.setMapperLocations(resolver.getResources(MAPPER_LOCATION));
        } catch (IOException e) {
            e.printStackTrace();
        }
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
}

pom.xml und application.yml werden oben weggelassen

    Verwendungsbeispiel
  • @Target({ ElementType.METHOD, ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TargetDataSource {
        String value();
    }

    Wenn unterschiedliche Ergebnisse zurückgegeben werden, ist es erfolgreich!

Das obige ist der detaillierte Inhalt vonWie SpringBoot das dynamische Umschalten mehrerer Datenquellen basierend auf AbstractRoutingDataSource implementiert. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen