1. 程式人生 > >springboot springjpa 從資料庫動態載入多資料來源 並隨意切換

springboot springjpa 從資料庫動態載入多資料來源 並隨意切換

背景:專案本來就是springboot+springJpa的框架,連線池用的德魯伊DruidDataSource。

想要在資料庫配置多資料來源,使用者可以隨便新增資料來源,根據請求帶的不同引數,controller層隨意切換資料來源,這樣就可以同一套部署的程式碼,操作不同的資料庫,有點 SaaS的味道,不過是各自獨立的庫。

1.配置預設資料來源

2.啟動之後載入資料庫的資料來源 並且載入到bean交給spring管理(資料來源增刪改的時候也要執行一遍本方法)

3.controller裡面指定資料來源。

1.德魯資料來源常見的springboot配置

  原有的配置是隻有primaryDataSource 這個 並且加了 Primary註解。那麼我們要加一個,預設Primary資料來源指向一個動態資料來源 即 dynamicDataSource。並且 注入我們的primaryDataSource。這樣 DynamicDataSource裡就有一個數據源了。程式碼如下。

DynamicDataSource繼承了AbstractRoutingDataSource,AbstractRoutingDataSource是springboot提供的資料來源路由。
@Configuration
public class DSConfig {
   
    @Bean(name = "primaryDataSource")
    @Qualifier("primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")

   public DataSource primaryDataSource(){
        return DruidDataSourceBuilder.create().build();
    }


    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dynamicDataSource(@Qualifier("primaryDataSource")DataSource dataSource){
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.myMap = new HashMap<>();//儲存我們有的資料來源,方便後面動態增加
        dynamicDataSource.myMap.put("1",dataSource);
        dynamicDataSource.setTargetDataSources(dynamicDataSource.myMap);//父類的方法
        dynamicDataSource.setDefaultTargetDataSource(dataSource);//父類的方法
        return  dynamicDataSource;
    }
}

2.啟動後加載資料庫配置的資料來源,新建一個類,注入查詢資料來源表的service和ApplicationContext。

用@PostConstruct 註解 來預設程式啟動即執行。(增刪改的時候同樣執行類似程式碼,此處省略)

   @Resource
   private SaasDataBase saasDataBase;
    @Autowired
    private ApplicationContext ctx;


    /**
     * 初始化
     */
    @PostConstruct
    public void init() {
//獲取BeanFactory
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) ctx.getAutowireCapableBeanFactory();
//建立bean資訊.
//迴圈saasDataBase從資料庫查出的資料來源列表,此處省略
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DruidDataSource.class);
        beanDefinitionBuilder.addPropertyValue("url", "jdbc:oracle:thin:@ip:1521:orcl");
        beanDefinitionBuilder.addPropertyValue("username", "XX");
        beanDefinitionBuilder.addPropertyValue("password", "XX");
        beanDefinitionBuilder.addPropertyValue("initialSize", 5);
        beanDefinitionBuilder.addPropertyValue("maxActive", 20);
        beanDefinitionBuilder.addPropertyValue("minIdle", 5);
        beanDefinitionBuilder.addPropertyValue("maxWait", 30000);
        //動態註冊bean. bean的名字為第一個引數、
        defaultListableBeanFactory.registerBeanDefinition("testDataSource",       beanDefinitionBuilder.getBeanDefinition());
//獲取動態註冊的bean.
        DataSource testDataSource =ctx.getBean("testDataSource",DruidDataSource.class);
        dynamicDataSource.myMap.put("2",testDataSource);//指定key為2的資料來源
        dynamicDataSource.setTargetDataSources(dynamicDataSource.myMap);
        dynamicDataSource.afterPropertiesSet();//一定要執行這個方法 具體含義可以看父類程式碼

3.資料來源已經都注入後,就剩下動態切換了,這個大家應該都看到過網上的案例  

DynamicDataSource 和 DynamicDataSourceContextHolder  實現動態切換資料來源的路由
public class DynamicDataSource extends AbstractRoutingDataSource {

    public Map<Object,Object> myMap = null;
    @Override

    protected Object determineCurrentLookupKey() {

        /*

         * DynamicDataSourceContextHolder程式碼中使用setDataSourceType

         * 設定當前的資料來源,在路由類中使用getDataSourceType進行獲取,

         *  交給AbstractRoutingDataSource進行注入使用。

         */

        return DynamicDataSourceContextHolder.getDataSourceType();

    }

}
public class DynamicDataSourceContextHolder {



    /*

     * 當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,

     * 所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。

     */

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



    public static List<String> dataSourceIds = new ArrayList<String>();





    public static void setDataSourceType(String dataSourceType) {

        contextHolder.set(dataSourceType);

    }



    public static String getDataSourceType() {

        return contextHolder.get();

    }



    public static void clearDataSourceType() {

        contextHolder.remove();

    }





    public static boolean containsDataSource(String dataSourceId){

        return dataSourceIds.contains(dataSourceId);

    }

}

以上兩個類  就是針對AbstractRoutingDataSource的利用,根據我們指定的 key 1為primaryDataSource這個bean key2為我們指定的testDataSource 這個bean,在controller或者其他地方 直接set就可以了。

//            DynamicDataSourceContextHolder.setDataSourceType("1");
//            DynamicDataSourceContextHolder.setDataSourceType("2");

這裡可以根據需要,根據判斷條件隨意設定。

以上3步寫好之後,就完美實現了spingboot對springjpa的動態資料來源切換。

springboot內建的預設EntityManager等東西不需要再具體配置,還是預設方式就行,因為他是基於@Primary 指定的這個 dynamicDataSource了。dynamicDataSource動態的變,一切都會隨著變。