Spring多資料來源獲取

乘風破浪醬發表於2019-04-03

思路

繼承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 重寫determineCurrentLookupKey方法

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

import javax.sql.DataSource;

public class MyDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return null;
    }
}

複製程式碼

例子

實現

主要涉及兩個類AbstractRoutingDataSource和DataSourceHolder

AbstractRoutingDataSource.java

public class DynamicDataSource extends AbstractRoutingDataSource{
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Inject
    private DataSourceManagerService dataSourceManagerService;

    @Inject
    @Qualifier("defaultCpsDataSource")
    private DataSource defaultCpsDataSource;

    @Inject
    private CpsDbProp cpsDbProp;

    @Inject
    private SysProp sysProp;

    @Inject
    private Caches caches;
    

    private Map<String, DruidDataSource> targetDataSources = new ConcurrentHashMap<>();

    @Override
    protected Object determineCurrentLookupKey() {
        String dsId = DataSourceHolder.getDataSource();
        logger.debug("[{}]連線到的資料來源id為:[{}]", Thread.currentThread().getName(), dsId);
        if(null == dsId){
            return null;
        }
        return getTargetDataSource(dsId);
    }

    /**
     * Retrieve the current target DataSource. Determines the
     * {@link #determineCurrentLookupKey() current lookup key}, performs
     * a lookup in the {@link #setTargetDataSources basicDataSources} map,
     * falls back to the specified
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
     * @see #determineCurrentLookupKey()
     */
    @Override
    protected DataSource determineTargetDataSource() {
        logger.debug("dynamicDataSource={}", this);
        DataSource dataSource = (DataSource) determineCurrentLookupKey();
        if (dataSource == null) {
            dataSource = defaultCpsDataSource;
        }
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource");
        }

        logger.debug("dataSource={}", dataSource);
        return dataSource;
    }

    /**
     * 獲取目標資料來源
     * @param dsId
     * @return
     */
    private DruidDataSource getTargetDataSource(String dsId) {
        if(null == dsId){
            return null;
        }

        if(Constants.Cache.ENABLE_CACHE.equals(sysProp.redisCacheEnable)){
            return getTargetDataSourceWithCache(dsId);
        }
        return getTargetDataSourceWithoutCache(dsId);
    }


    private DruidDataSource getTargetDataSourceWithoutCache(String dsId) {
        DruidDataSource druidDataSource = targetDataSources.get(dsId);
        if (druidDataSource != null) return druidDataSource;
        synchronized (this) {
            druidDataSource = targetDataSources.get(dsId);
            if (druidDataSource != null) return druidDataSource;
            DataSourceRuntimeMeta dataSourceRuntimeMeta = dataSourceManagerService.getDataSourceRuntimeMeta(dsId);
            druidDataSource = buildBasicDataSource(dsId, dataSourceRuntimeMeta);
        }
        return druidDataSource;
    }

    //資料來源修改時怎麼做? 去拿一次快取,快取存在的話則獲取,否則更新
    private DruidDataSource getTargetDataSourceWithCache(String dsId){
        DruidDataSource druidDataSource;
        String obj = caches.getThenSetExpired(String.format(Constants.Cache.DATA_SOURCE, dsId));

        if(obj != null){
            druidDataSource = targetDataSources.get(dsId);
            if(druidDataSource != null) return druidDataSource;
        }
        synchronized (this) {
            obj = caches.getThenSetExpired(String.format(Constants.Cache.DATA_SOURCE, dsId));
            if(obj != null){
                druidDataSource = targetDataSources.get(dsId);
                if(druidDataSource != null) return druidDataSource;
            }
            targetDataSources.remove(dsId);
            DataSourceRuntimeMeta dataSourceRuntimeMeta = dataSourceManagerService.getDataSourceRuntimeMeta(dsId);
            druidDataSource = buildBasicDataSource(dsId, dataSourceRuntimeMeta);
        }
        return druidDataSource;
    }

    private DruidDataSource buildBasicDataSource(String dsId, DataSourceRuntimeMeta dataSourceRuntimeMeta) {
        if(null == dataSourceRuntimeMeta){
            logger.warn("DataSourceRuntimeMeta is null, dsId=[{}],請確認該資料來源是否存在或者停用!", dsId);
            return null;
        }

        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUsername(dataSourceRuntimeMeta.getUserName());
        druidDataSource.setPassword(dataSourceRuntimeMeta.getPassword());
        druidDataSource.setUrl(dataSourceRuntimeMeta.getDbParameter());
        druidDataSource.setDriverClassName(cpsDbProp.driver);

        //druidDataSource.setMaxIdle(cpsDbProp.maxIdle);/*druid已經不再使用,配置了也沒效果*/
        druidDataSource.setMinIdle(cpsDbProp.minIdle);
        druidDataSource.setMaxWait(cpsDbProp.maxWait);
        druidDataSource.setValidationQuery(cpsDbProp.validateSql);
        druidDataSource.setMinEvictableIdleTimeMillis(cpsDbProp.minEvictableIdleTimeMillis);
        druidDataSource.setTimeBetweenEvictionRunsMillis(cpsDbProp.timeBetweenEvictionRunsMillis);

        if(null != dataSourceRuntimeMeta.getMaxActive()){
            druidDataSource.setMaxActive(dataSourceRuntimeMeta.getMaxActive());
        }else {
            druidDataSource.setMaxActive(cpsDbProp.maxActive);
        }

        if(null != dataSourceRuntimeMeta.getQueryTimeout()){
            druidDataSource.setQueryTimeout(dataSourceRuntimeMeta.getQueryTimeout());
        }else {
           druidDataSource.setQueryTimeout(cpsDbProp.queryTimeout);
        }
        druidDataSource.setValidationQuery(cpsDbProp.validateSql);
        druidDataSource.setTestOnBorrow(true);

        targetDataSources.putIfAbsent(dsId, druidDataSource);
        return druidDataSource;
    }
    




}

複製程式碼

DataSourceHolder.java

public class DataSourceHolder {
    private static final Logger logger = LoggerFactory.getLogger(DataSourceHolder.class);

    //用來存放執行緒資訊
    private static final ThreadLocal<String> dsHolder = new ThreadLocal<>();

    public static void setDataSource(String dsId){
        logger.debug("[{}] setDataSource,dsId=[{}]", Thread.currentThread().getName(), dsId);
        dsHolder.set(dsId);
    }

    public static String getDataSource(){
        logger.debug("[{}] getDataSource", Thread.currentThread().getName());
        return dsHolder.get();
    }

    public static void clearDataSource(){
        logger.debug("[{}] clearDataSource", Thread.currentThread().getName());
        dsHolder.remove();
    }
}
複製程式碼

單元測試

public class DynamicDataSourceTest extends BaseTest {
    ExecutorService executorService = Executors.newFixedThreadPool(20);

    @Autowired
    QueryTest queryTest;

    @Inject
    DataSourceManagerMapper dataSourceManagerMapper;

    @Inject
    private DsRunningStateCache dsRunningStateCache;

    private int MAX_SIZE = 1;
    private String[] dsIds = {"caa8ef43e1be475985d08c3fef873291","1","d5a87b23852543279b1a517dc408630b"};


    @Test
    public void test(){
        CountDownLatch countDownLatch = new CountDownLatch(MAX_SIZE);
        AtomicInteger atomicInteger = new AtomicInteger(0);
        for(int i = 0;i < MAX_SIZE;++i){
            executorService.execute(
                ()->{
                    int idx = atomicInteger.incrementAndGet();
                    String dsId = dsIds[idx % dsIds.length];
                    try{
                        DataSourceHolder.setDataSource(dsId);
                        dsRunningStateCache.setDsRunningState(dsId, DataSourceRunningState.RUNNING.getCode());
                        queryTest.queryTest3();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    finally {
                        DataSourceHolder.clearDataSource();
                        dsRunningStateCache.setDsRunningState(dsId, DataSourceRunningState.STOP.getCode());
                        countDownLatch.countDown();
                    }
                }
            );
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

複製程式碼

相關文章