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動態的變,一切都會隨著變。