最代碼廣告位
hekf2022的gravatar頭像
hekf20222019-10-16 23:39:02
Springboot動態切換數據庫

很多情況下,希望應用程序搭建一套,為每個用戶建立一個私有的數據庫,所有程序使用一套.

開動吧:

一、 首先繼承AbstractRoutingDataSource,從名稱上看為抽象路由數據源,就是spring為提供動態數據庫而設定的。在這個類中,需要重寫determineCurrentLookupKey這個方法,這個方法就是動態從

private Map<Object, Object> targetDataSources(AbstractRoutingDataSource類里面的屬性)里面獲取對應的數據源
 

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
@Configuration
public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * 每次請求動態請求哪一個數據源
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSource();
    }
 
    public DynamicDataSource(){
         Map<Object, Object> dataSources=new ConcurrentHashMap<>();
        for(int i=1;i<=4;i++){
            DataSource dataSource = druidDataSource(i);
            dataSources.put(String.valueOf(i),dataSource);
            if(i==1){
                super.setDefaultTargetDataSource(dataSource);
            }
        }
        super.setTargetDataSources(dataSources);
    }
    /**
     * 此處數據庫信配置,可以來源于redis等,然后再初始化所有數據源
     * 重點說明:一個DruidDataSource數據源,它里面本身就是線程池了,
     * 所以我們不需要考慮線程池的問題
     * @param no
     * @return
     */
    public DataSource druidDataSource(int no) {
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl("jdbc:mysql://localhost:3306/ds0"+no);
        datasource.setUsername("root");
        datasource.setPassword("123456");
        datasource.setDriverClassName("com.mysql.jdbc.Driver");
        datasource.setInitialSize(5);
        datasource.setMinIdle(5);
        datasource.setMaxActive(20);
        //datasource.setDbType("com.alibaba.druid.pool.DruidDataSource");
        datasource.setMaxWait(60000);
        datasource.setTimeBetweenEvictionRunsMillis(60000);
        datasource.setMinEvictableIdleTimeMillis(300000);
        datasource.setValidationQuery("SELECT 1 FROM DUAL");
        datasource.setTestWhileIdle(true);
        datasource.setTestOnBorrow(false);
        datasource.setTestOnReturn(false);
        try {
            datasource.setFilters("stat,wall,log4j");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return datasource;
    }
}

二、新建DataSourceHolder:

public class DataSourceHolder {
    //線程本地環境
    private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
    //設置數據源,動態切換,就是調用這個setDataSource方法
    public static void setDataSource(String customerType) {
        dataSources.set(customerType);
    }
    //獲取數據源
    public static String getDataSource() {
        return (String) dataSources.get();
    }
    //清除數據源
    public static void clearDataSource() {
        dataSources.remove();
    }
}

三、在controller層的每個方法前切換數據源,用到AOP

重點說明:每多人會有疑問,springcloud的controller是單例,這樣在多用戶的情況下,會不會竄請求,線程不安全

原因解答:(1)請看上面,其實我們使用了ThreadLocal,如果不理解的,請去補ThreadLocal知識了

用多線程測試:我用多個線程,調用同一個查詢方法,但每個請求的header中的dsNo都不一樣,這樣就真實模擬生產環境,多用戶查的情況,看是否有有竄請求,線程不安全的情況,實際測試并沒有這種情況出現

(1)AOP動態切換數據庫:
 

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
 
 
@Aspect
@Order(1)
@Configuration
public class DataSourceAspect {
    private static final String dsNo="dsNo";//數據庫編號 從header中取
 
    /**
     * 切入點,放在controller的每個方法上進行切入,更新數據源
     */
    @Pointcut("execution(* com.eck.auto.controller..*.*(..))")
    private void anyMethod(){}//定義一個切入點
    @Before("anyMethod()")
    public void dataSourceChange()
    {
        //請求頭head中獲取對應數據庫編號
        String no = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader(dsNo);
        System.out.print("當前數據源編號:"+no);
        if(StringUtils.isEmpty(no)){
            //TODO 根據業務拋異常
        }
        DataSourceHolder.setDataSource(no);
        /*這里數據庫項目編號來更改對應的數據源*/
    }
}

四、相信很多人都希望要本項目代碼,本項目githup地址

https://github.com/hekf2021/autodatasource.git

測試說明:按本項目測試,需要創建4個數據庫ds01、ds02、ds03、ds04,sql語句在sql.sql中,里面表都一樣,只是數據不一樣,比如ds01庫表中的記錄reason寫dso1,ds02數據庫中表的記錄寫ds02,ds03數據庫中表的記錄寫ds03,ds04數據庫中表的記錄寫ds04,以此類推,自己修改數據...,通過查詢傳不同的數據庫編號,返回的不同數據,來辨別是否切換到了對應的數據庫

如下圖(如有什么不對的對方,可以加Q群 147960493,聯系群主我)

補充信息:

在實際項目中如何應用,提示一下大家,1、首先得有一個公共全局數據庫,如all_database_url,這里面存所有數據源的jdbc url地址,以及連對應數據庫的用戶名及密碼。2、修改DynamicDataSource 中的DynamicDataSource()方法從all_database_url庫中取出所有數據庫的jdbc url信息,進行初始化。3、DynamicDataSource()方法從all_database_url庫中取出所有數據庫的jdbc url信息,怎么取?自己寫原生代碼查詢數據all_database_url,不能用框架了,原因是框架連數據庫,給ds01,ds02,ds03這些具體數據庫用了。
 


打賞

分享到:

最近瀏覽
uilpassword LV111月26日
星星
暫無貢獻等級
仙身的羊 LV911月22日
月亮月亮星星
wuyanying LV111月14日
星星
lvxiaomao11月9日
暫無貢獻等級
遇見, LV3611月8日
太陽太陽月亮
liuwen_7777 LV811月8日
月亮月亮
406971727 LV1611月5日
太陽
exam_code11月5日
暫無貢獻等級
woldxy LV511月4日
月亮星星
最代碼廣告位
頂部客服微信二維碼底部
>掃描二維碼關注最代碼為好友掃描二維碼關注最代碼為好友
福彩3d组选020前后关系