Dynamic switching data source summary learning

        To achieve dynamic data source switching, the abstract class AbstractRoutingDataSource of the Spring framework is inseparable. This is the key to realizing dynamic data source switching. Let's look at this abstract class first.

        The source code of AbstractRoutingDataSource is as follows:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.jdbc.datasource.lookup;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    @Nullable
    private Map<Object, Object> targetDataSources;
    @Nullable
    private Object defaultTargetDataSource;
    private boolean lenientFallback = true;
    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
    @Nullable
    private Map<Object, DataSource> resolvedDataSources;
    @Nullable
    private DataSource resolvedDefaultDataSource;

    public AbstractRoutingDataSource() {
    }

    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        this.targetDataSources = targetDataSources;
    }

    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        this.defaultTargetDataSource = defaultTargetDataSource;
    }

    public void setLenientFallback(boolean lenientFallback) {
        this.lenientFallback = lenientFallback;
    }

    public void setDataSourceLookup(@Nullable DataSourceLookup dataSourceLookup) {
        this.dataSourceLookup = (DataSourceLookup)(dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
    }

    public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        } else {
            this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
            this.targetDataSources.forEach((key, value) -> {
                Object lookupKey = this.resolveSpecifiedLookupKey(key);
                DataSource dataSource = this.resolveSpecifiedDataSource(value);
                this.resolvedDataSources.put(lookupKey, dataSource);
            });
            if (this.defaultTargetDataSource != null) {
                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
            }

        }
    }

    protected Object resolveSpecifiedLookupKey(Object lookupKey) {
        return lookupKey;
    }

    protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
        if (dataSource instanceof DataSource) {
            return (DataSource)dataSource;
        } else if (dataSource instanceof String) {
            return this.dataSourceLookup.getDataSource((String)dataSource);
        } else {
            throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
        }
    }

    public Map<Object, DataSource> getResolvedDataSources() {
        Assert.state(this.resolvedDataSources != null, "DataSources not resolved yet - call afterPropertiesSet");
        return Collections.unmodifiableMap(this.resolvedDataSources);
    }

    @Nullable
    public DataSource getResolvedDefaultDataSource() {
        return this.resolvedDefaultDataSource;
    }

    public Connection getConnection() throws SQLException {
        return this.determineTargetDataSource().getConnection();
    }

    public Connection getConnection(String username, String password) throws SQLException {
        return this.determineTargetDataSource().getConnection(username, password);
    }

    public <T> T unwrap(Class<T> iface) throws SQLException {
        return iface.isInstance(this) ? this : this.determineTargetDataSource().unwrap(iface);
    }

    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return iface.isInstance(this) || this.determineTargetDataSource().isWrapperFor(iface);
    }

    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource 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 + "]");
        } else {
            return dataSource;
        }
    }

    @Nullable
    protected abstract Object determineCurrentLookupKey();
}

        First, the AbstractRoutingDataSource abstract class inherits the AbstractDataSource abstract class and implements the InitializingBean interface. The InitializingBean interface provides an afterPropertiesSet() method, which will be executed when the Bean is initialized for any class that inherits the interface. So afterPropertiesSet in the AbstractRoutingDataSource class is used to initialize some information. To understand the interface of InitializingBean, you can refer to my blog: The role of InitializingBean in the Spring framework 

        Secondly, this class AbstractRoutingDataSource class has an abstract method, which needs to be implemented by us. The function of this method is to specify which data source to use, that is, to tell the dao layer to use the data source we specified to connect to the data source now.

                

protected abstract Object determineCurrentLookupKey();

        AbstractRoutingDataSource also inherits the abstract class AbstractDataSource, and the AbstractDataSource abstract class inherits the DataSource interface. There are only two methods in DataSource, and these two methods will be implemented in the AbstractDataSource abstract class.
        

        Now let's see what some properties in AbstractRoutingDataSource do

@Nullable
    private Map<Object, Object> targetDataSources;  目标数据源,也就是我们要切换的多个数据源都存放到这里
    @Nullable
    private Object defaultTargetDataSource; 默认数据源,如果想指定默认数据源,可以给它赋值
    private boolean lenientFallback = true;
    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
    @Nullable
    private Map<Object, DataSource> resolvedDataSources;存放真正的数据源信息,将targetDataSources的信息copy一份
    @Nullable
    private DataSource resolvedDefaultDataSource; 默认数据源,将defaultTargetDataSource转为DataSource赋值给resolvedDefaultDataSource

    public AbstractRoutingDataSource() {
    }

           The afterPropertiesSet() method in the AbstractRoutingDataSource abstract class is the core, so let's see what initialization operations he has done.

        

public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        } else {
            this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
            this.targetDataSources.forEach((key, value) -> {
                Object lookupKey = this.resolveSpecifiedLookupKey(key);
                DataSource dataSource = this.resolveSpecifiedDataSource(value);
                this.resolvedDataSources.put(lookupKey, dataSource);
            });
            if (this.defaultTargetDataSource != null) {
                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
            }

        }
    }

        First, when targetDataSources is empty, an IllegalArgumentException error will be thrown, so we need to pass in at least one data source when configuring multiple data sources.

        Second, the size of resolvedDataSources is initialized. ResolvedDataSources just copies the content of targetDataSources. The difference is that the value of targetDataSources is Object type, while the value of resolvedDataSources is DataSource type.
        Third, traverse the targetDataSources collection, then call the resolveSpecifiedLookupKey() method and resolveSpecifiedDataSource() method, and finally put the return value into the resolvedDataSources collection as Key-Value.

        Fourth, what do the methods resolveSpecifiedLookupKey() and method resolveSpecifiedDataSource() do respectively? We can look at the implementation of both methods. In fact, you can see that resolveSpecifiedLookupKey directly returns the value without doing any operations, while resolveSpecifiedDataSource just converts the Object into a DataSource object and returns it.
        

        So the initial words of afterPropertiesSet() are to convert the content of targetDataSources into resolvedDataSources, convert defaultTargetDataSource to DataSource and assign it to resolvedDefaultDataSource .

        Let's look at how the getConnection() method is implemented. The determineTargetDataSource() method is called .

        

public Connection getConnection() throws SQLException {
        return this.determineTargetDataSource().getConnection();
    }

        So what exactly does the determineTargetDataSource method do, we can look at the implementation of determineTargetDataSource. This method returns an object of type DataSource, and calls the determineCurrentLookupKey method. Maybe someone finds out that this method is the method to be implemented by our custom data source class. determineCurrentLookupKey returns an Object, named lookupKey, and uses lookupKey as a key to get the data source from the resolvedDataSources collection. If the data source is not obtained, it will take the default data source resolvedDefaultDataSource. If you still don't get it, report an error at this time!

        Through the above analysis, when is the method determineCurrentLookupKey() implemented by ourselves not called? From a logical point of view, determineCurrentLookupKey() is called by determineTargetDataSource(), and determineTargetDataSource() is called by getConnection(), so who and when is the getConnection() method called? Through code tracking, it is called at this time when we call the dao layer method in the service layer.

        

        After understanding the abstract class AbstractRoutingDataSource, it is time to implement a dynamic data source switching function. The source code involved is as follows:

        

package com.lsl.mylsl.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 动态数据源实现类
 */
public class DynamicDataSource extends AbstractRoutingDataSource {


    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = DBContextHolder.getDataSource();
        if (dataSource == null || "".equals(dataSource)){
            dataSource = "ds1";
        }
        return dataSource;
    }
}

        When no data source is specified, ds1 is specified as the current data source by default

        

package com.lsl.mylsl.config;

public class DBContextHolder {

    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 动态切换数据源
     * @param dataSource
     */
    public static void setDataSource(String dataSource){

        if (SpringbootConfig.DBMAP.containsKey(dataSource)){
            CONTEXT_HOLDER.set(dataSource);
        }else {
            System.out.println("数据源" + dataSource + "不存在");
        }
    }

    /**
     * 获取数据源
     * @return
     */
    public static String getDataSource(){
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空数据源
     */
    public static void clearDataSource(){
        CONTEXT_HOLDER.remove();
    }
}

        

        

package com.lsl.mylsl.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 初始化多个数据源
 * 注册动态数据源
 */
@Configuration
public class SpringbootConfig {

    public static final Map<Object,Object> DBMAP = new HashMap<>();

    /**
     * 初始化数据源1
     */
    public void initDataSource1(){
        try {
            DruidDataSource dds1 = DruidDataSourceBuilder.create().build();
            dds1.setUsername("admin");
            dds1.setPassword("123456");
            dds1.setUrl("jdbc:oracle:thin:@10.10.10.10:1521/oracle1");
            dds1.setInitialSize(5);
            dds1.setMinIdle(5);
            dds1.setMaxActive(20);
            dds1.setMaxWait(60000);
            dds1.setTimeBetweenEvictionRunsMillis(60000);
            dds1.setMinEvictableIdleTimeMillis(300000);
            dds1.setValidationQuery("SELECT * FROM DUAL");
            dds1.setTestWhileIdle(true);
            dds1.setTestOnBorrow(false);
            dds1.setTestOnReturn(false);
            dds1.setMaxPoolPreparedStatementPerConnectionSize(20);
            dds1.setFilters("stat,wall");
            dds1.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slow.slowSqlMillis=5000");
            dds1.setUseGlobalDataSourceStat(true);
            //添加数据源到map
            SpringbootConfig.DBMAP.put("ds1",dds1);
        } catch (Exception e) {

        }


    }

    /**
     * 初始化数据源2
     */
    public void initDataSource2(){
        try {
            DruidDataSource dds2 = DruidDataSourceBuilder.create().build();
            dds2.setUsername("admin");
            dds2.setPassword("123456");
            dds2.setUrl("jdbc:mysql://10.10.10.10:1521/mysql1");
            dds2.setInitialSize(5);
            dds2.setMinIdle(5);
            dds2.setMaxActive(20);
            dds2.setMaxWait(60000);
            dds2.setTimeBetweenEvictionRunsMillis(60000);
            dds2.setMinEvictableIdleTimeMillis(300000);
            dds2.setValidationQuery("SELECT 1");
            dds2.setTestWhileIdle(true);
            dds2.setTestOnBorrow(false);
            dds2.setTestOnReturn(false);
            dds2.setMaxPoolPreparedStatementPerConnectionSize(20);
            dds2.setFilters("stat,wall");
            dds2.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slow.slowSqlMillis=5000");
            dds2.setUseGlobalDataSourceStat(true);
            dds2.setDbType("mysql");
            //添加数据源到map
            SpringbootConfig.DBMAP.put("ds2",dds2);
        } catch (Exception e) {

        }
    }

    /**
     * 把DynamicDataSource交给spring托管
     * @return
     */
    @Bean
    public DataSource dynamicDataSource(){
        DynamicDataSource myDs = new DynamicDataSource();
        initDataSource1();
        initDataSource2();
        myDs.setTargetDataSources(SpringbootConfig.DBMAP);
        myDs.afterPropertiesSet();
        return myDs;
    }

    /**
     * 事务管理
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

        How to implement data source switching, the code is very simple, as follows:

package com.lsl.mylsl.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lsl.mylsl.BO.CatBO;
import com.lsl.mylsl.config.DBContextHolder;
import com.lsl.mylsl.mapper.CatMapper;
import com.lsl.mylsl.service.ICatService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
public class CatServiceImpl extends ServiceImpl<CatMapper, CatBO> implements ICatService {

    @Autowired
    CatMapper catMapper;

    @Override
    public List<Map> qryCatByAge(Map params) {
        //切换数据源到ds2
        DBContextHolder.setDataSource("ds2");
        return catMapper.qryCatByAge(params);
    }
}

おすすめ

転載: blog.csdn.net/dhklsl/article/details/127671314