當前位置:首頁 » 操作系統 » 動態切換資料庫

動態切換資料庫

發布時間: 2022-05-31 23:06:59

『壹』 如何在Spring代碼中動態切換數據源

最近項目中遇到一個場景,需要能夠在一個方法中操作多個具有相同表結構資料庫(你可以理解為一個表中的數據被水平拆分到多個庫中,查詢時需要遍歷這多個庫)。經過筆者幾天的研究,最終解決了問題,並且寫了一個demo共享到我的github。

關注筆者博客的小夥伴一定知道之前的這篇文章點擊打開鏈接,這篇博客中的解決方案僅僅適用讀寫分離的場景。就是說,當你在開發的時候已經確定了使用寫庫一讀庫的形式。筆者今天要寫的這篇文章具有普適性,適合所有需要在Spring工程中動態切換數據源的場景,而且本文中的解決方案對工程的代碼基本沒有侵入性。下面就來說下該方案的實現原理:

在Spring-Mybatis中,有這樣一個類AbstractRoutingDataSource,根據名字可以猜到,這是一個框架提供的用於動態選擇數據源的類。這個類有兩個重要的參數,分別叫

defaultTargetDataSource和targetDataSources。一般的工程都是一個數據源,所以不太接觸到這個類。在作者之前的博客自動切換多個數據源中,可以看到這個類的xml配置如下:

[html] view plain
<bean id="myoneDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.myone.driver}"/>
<property name="url" value="${jdbc.myone.url}"/>
<property name="username" value="${jdbc.myone.username}"/>
<property name="password" value="${jdbc.myone.password}"/>
</bean>
<bean id="mytwoDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.mytwo.driver}"/>
<property name="url" value="${jdbc.mytwo.url}"/>
<property name="username" value="${jdbc.mytwo.username}"/>
<property name="password" value="${jdbc.mytwo.password}"/>
</bean>

<bean id="multipleDataSource" class="dal.datasourceswitch.MultipleDataSource">
<property name="defaultTargetDataSource" ref="myoneDataSource"/> <!--默認主庫-->
<property name="targetDataSources">
<map>
<entry key="myone" value-ref="myoneDataSource"/> <!--輔助aop完成自動資料庫切換-->
<entry key="mytwo" value-ref="mytwoDataSource"/>
</map>
</property>
</bean>

上面的配置文件對這兩個參數的描述已經很清楚了,但這是多個數據源已經確定的場景。我們這篇博客中的場景是多個數據源的信息存在於資料庫中,可能資料庫中的數據源信息會動態的增加或者減少。這樣的話,就不能像上面這樣配置了。那怎麼辦呢?
我們僅僅需要設定默認的數據源,即defaultDataSource參數,至於targetDataSources參數我們需要在代碼中動態的設定。來看下具體的xml配置:

[html] view plain
<bean id="defaultDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
p:driverClassName="${db_driver}"
p:url="${db_url}"
p:username="${db_user}"
p:password="${db_pass}"
p:validationQuery="select 1"
p:testOnBorrow="true"/>

<!--動態數據源相關-->
<bean id="dynamicDataSource" class="org.xyz.test.service.datasourceswitch.impl.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="defaultDataSource" value-ref="defaultDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="defaultDataSource"/>
</bean>

從上面的配置文件中可以看到,我們僅僅配置了默認的數據源defaultDataSource。至於其他的數據源targetDataSources,我們沒有配置,需要在代碼中動態的創建。關於配置就講清楚啦!但我們注意到,支持動態數據源的不應該是AbstractRoutingDataSource類嗎?怎麼上面的配置中是DynamicDataSource類。沒錯,這個是我們自定義的繼承自AbstractRoutingDataSource類的類,也只最重要的類,來看下:(理解這個類,你需要熟練掌握JAVA反射,以及ThreadLocal變數,和Spring的注入機制。別退縮,大家都是這樣一步步學過來的!)(下面僅僅是看下全貌,代碼的下面會有詳細的說明)
[java] view plain
final class DynamicDataSource extends AbstractRoutingDataSource implements ApplicationContextAware{

private static final String DATA_SOURCES_NAME = "targetDataSources";

private ApplicationContext applicationContext;

@Override
protected Object determineCurrentLookupKey() {
DataSourceBeanBuilder dataSourceBeanBuilder = DataSourceHolder.getDataSource();
System.out.println("----determineCurrentLookupKey---"+dataSourceBeanBuilder);
if (dataSourceBeanBuilder == null) {
return null;
}
DataSourceBean dataSourceBean = new DataSourceBean(dataSourceBeanBuilder);
//查看當前容器中是否存在
try {
Map<Object,Object> map=getTargetDataSources();
synchronized (this) {
if (!map.keySet().contains(dataSourceBean.getBeanName())) {
map.put(dataSourceBean.getBeanName(), createDataSource(dataSourceBean));
super.afterPropertiesSet();//通知spring有bean更新
}
}
return dataSourceBean.getBeanName();
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new SystemException(ErrorEnum.MULTI_DATASOURCE_SWITCH_EXCEPTION);
}
}

private Object createDataSource(DataSourceBean dataSourceBean) throws IllegalAccessException {
//在spring容器中創建並且聲明bean
context = () applicationContext;
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(BasicDataSource.class);
//將dataSourceBean中的屬性值賦給目標bean
Map<String, Object> properties = getPropertyKeyValues(DataSourceBean.class, dataSourceBean);
for (Map.Entry<String, Object> entry : properties.entrySet()) {
beanDefinitionBuilder.addPropertyValue((String) entry.getKey(), entry.getValue());
}
beanFactory.registerBeanDefinition(dataSourceBean.getBeanName(), beanDefinitionBuilder.getBeanDefinition());
return applicationContext.getBean(dataSourceBean.getBeanName());
}

private Map<Object, Object> getTargetDataSources() throws NoSuchFieldException, IllegalAccessException {
Field field = AbstractRoutingDataSource.class.getDeclaredField(DATA_SOURCES_NAME);
field.setAccessible(true);
return (Map<Object, Object>) field.get(this);
}

private <T> Map<String, Object> getPropertyKeyValues(Class<T> clazz, Object object) throws IllegalAccessException {
Field[] fields = clazz.getDeclaredFields();
Map<String, Object> result = new HashMap<>();
for (Field field : fields) {
field.setAccessible(true);
result.put(field.getName(), field.get(object));
}
result.remove("beanName");
return result;
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}

首先來看覆蓋方法determineCurrentLookupKey(),框架在每次調用數據源時會先調用這個方法,以便知道使用哪個數據源。在本文的場景中,數據源是由程序員在即將切換數據源之前,將要使用的那個數據源的名稱放到當前線程的ThreadLocal中,這樣在determineCurrentLookupKey()方法中就可以從ThreadLocal中拿到當前請求鑰匙用的數據源,從而進行初始化數據源並返回該數據源的操作。在ThreadLocal變數中,我們保存了一個DataSourceBuilder,這是一個建造者模式,不是本文的關鍵。我們在後面說。讀者直接把他理解為是一個數據源的描述就好。因此,determineCurrentLookupKey()方法的流程就是:先從ThreadLocal中拿出要使用的數據源信息,然後看當前的targetDataSources中是否有了這個數據源。如果有直接返回。如果沒有,創建一個這樣的數據源,放到targetDataSources中然後返回。這個過程需要加鎖,為何?這是典型的判斷後插入場景,在多線程中會有線程安全問題,所以要加鎖!至囑!

由於targetDataSources是父類AbstractRoutingDataSource中的一個私有域,因此想要獲得他的實例只能通過Java反射機制。這也是下面的方法存在的意義!

[java] view plain
private Map<Object, Object> getTargetDataSources() throws NoSuchFieldException, IllegalAccessException {
Field field = AbstractRoutingDataSource.class.getDeclaredField(DATA_SOURCES_NAME);
field.setAccessible(true);
return (Map<Object, Object>) field.get(this);
}

然後,我們來看具體是怎麼創建數據源的。
[java] view plain
private Object createDataSource(DataSourceBean dataSourceBean) throws IllegalAccessException {
//在spring容器中創建並且聲明bean
context = () applicationContext;
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(BasicDataSource.class);
//將dataSourceBean中的屬性值賦給目標bean
Map<String, Object> properties = getPropertyKeyValues(DataSourceBean.class, dataSourceBean);
for (Map.Entry<String, Object> entry : properties.entrySet()) {
beanDefinitionBuilder.addPropertyValue((String) entry.getKey(), entry.getValue());
}
beanFactory.registerBeanDefinition(dataSourceBean.getBeanName(), beanDefinitionBuilder.getBeanDefinition());
return applicationContext.getBean(dataSourceBean.getBeanName());
}

大家知道,Spring最主要的功能是作為bean容器,即他負責bean生命周期的管理。因此,我們自定義的datasource也不能「逍遙法外」,必須交給Spring容器來管理。這也正是DynamicDataSource類需要實現ApplicationContextAware並且注入ApplicationContext的原因。上面的代碼就是根據指定的信息創建一個數據源。這種創建是Spring容器級別的創建。創建完畢之後,需要把剛剛創建的這個數據源放到targetDataSources中,並且還要通知Spring容器,targetDataSources對象變了。下面的方法就是在做這樣的事情:
[java] view plain
private void (DataSourceBean dataSourceBean) throws NoSuchFieldException, IllegalAccessException {
getTargetDataSources().put(dataSourceBean.getBeanName(), createDataSource(dataSourceBean));
super.afterPropertiesSet();//通知spring有bean更新
}
上面的這一步很重要。沒有這一步的話,Spring壓根就不會知道targetDataSources中多了一個數據源。至此DynamicDataSource類就講完了。其實仔細想想,思路還是很清晰的。啃掉了DynamicDataSource類這塊硬骨頭,下面就是一些輔助類了。比如說DataSourceHolder,業務代碼通過使用這個類來通知DynamicDataSource中的determineCurrentLookupKey()方法到底使用那個數據源:
[java] view plain
public final class DataSourceHolder {
private static ThreadLocal<DataSourceBeanBuilder> threadLocal=new ThreadLocal<DataSourceBeanBuilder>(){
@Override
protected DataSourceBeanBuilder initialValue() {
return null;
}
};

static DataSourceBeanBuilder getDataSource(){
return threadLocal.get();
}

public static void setDataSource(DataSourceBeanBuilder dataSourceBeanBuilder){
threadLocal.set(dataSourceBeanBuilder);
}

public static void clearDataSource(){
threadLocal.remove();
}
}

『貳』 java代碼出錯,spring動態切換資料庫

配置文件是否正確 。。。。。。。。動態切換,得重新初始化 ~

『叄』 spring配置多數據源進行動態切換,資料庫方言設置那個,因為資料庫是oracle和mysql

<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
>

<!-- dataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">

<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:oracle"></property>
<property name="username" value="scott"></property>
<property name="password" value="tiger"></property>
</bean>

<!-- jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">

<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>

</beans>

『肆』 多個mongoDB數據源,怎麼配置動態切換

1:配置多個數據連接,如下
dbUrl = 'mongodb://'+configs.dbconfig.dbmongodbhealth.host+':'
+ configs.dbconfig.dbmongodbhealth.port.toString() +'/'+configs.dbconfig.dbmongodbhealth.dbname ;
var healthdbtoptions = {db: {},
server: { poolSize: 5 , auto_reconnect:true } ,
replSet: {},
mongos: {}

} ;
MongoClient.connect(dbUrl,healthdbtoptions ,function(err, database) {
if(err) throw err;
dbs.healthdb = database ;
console.info(dbUrl + " open ok healthdb");
//
});
2:調用數據訪問的時候動態傳遞要使用的鏈接
var publicdb = opts.dbs.publicdb ;
--------------
exports.getAreaByPcodeAsync = function(opts){
var results = {error_code:-1,error_msg:"error"} ;
var bbPromise = opts.dbs.bbpromise ;
var collectionName = "D10002";
var publicdb = opts.dbs.publicdb ;
var filter = {pcode:opts.area.pcode}
return publicdb.collection(collectionName).find(filter).toArrayAsync().
then(function(areas){
var selectArray = [];

});
};

『伍』 ssh框架中,如何動態切換資料庫

每個項目用不同的資料庫連接再加一個登陸的項目(只是做登陸然後根據用戶的登陸的選擇把請求的用戶名和密碼請求跳轉到到不同的項目路徑下的登陸驗證,如果驗證成功則把匹配項目的路徑地址返回,記得在那個匹配好了的項目中的session中保存好個人信息),開始的登陸界面收到返回的跳轉地址好跳轉到相應的項目ps:我沒有實現過,只是有這個想法,技術能實現不就你試一下

『陸』 完全不依靠xml文件,怎麼配置mybatis 說是動態切換資料庫,還是什麼……

完全不依靠xml文件,怎麼配置m依賴包的引入。創建好maven項目之後,打開maven項目下的pom.xml文件,在配置文件中增加對mybatis包和mysql驅動包的依賴。在dependencies中增加對這兩個包的配置即可自動將這兩個包添加到項目中。

mybatis資料庫配置。對於mybatis框架來說,首先需要配置的就是資料庫的數據源配置以及採用何種開發模式的配置,對於mavne項目來說這些資源信息都需要配置在src/main/resources下面,對於普通的java項目則只需要配置在src下即可。ybatis 說是動態切換資料庫,還是什麼

『柒』 多個mongoDB數據源,怎麼配置動態切換

Spring動態配置多數據源,即在大型應用中對數據進行切分,並且採用多個資料庫實例進行管理,這樣可以有效提高系統的水平伸縮性。而這樣的方案就會不同於常見的單一數據實例的方案,這就要程序在運行時根據當時的請求及系統狀態來動態的決定將數據存儲在哪個資料庫實例中,以及從哪個資料庫提取數據。

『捌』 mybatis plus自定義的mapper如何動態切換數據源

在配置文件更換資料庫路徑即可

『玖』 怎麼使用spring或者hibernate實現對資料庫的動態切換,最好只需要編程不要使用配置文件

你就是用jdbc來實現吧,寫一個 .properties文件進行配置,手動改之間的鏈接,每次獲取一個新的connection對象即可了,記得把之前獲取到對象關閉即可。方便你的操作。

熱點內容
python3哪個版本好 發布:2025-01-11 05:07:29 瀏覽:864
手機怎麼訪問外網 發布:2025-01-11 05:07:27 瀏覽:532
財務信息伺服器搭建 發布:2025-01-11 04:48:09 瀏覽:875
演算法實現過程 發布:2025-01-11 04:43:45 瀏覽:457
瞄準下載ftp 發布:2025-01-11 04:43:44 瀏覽:573
校園電影腳本 發布:2025-01-11 04:32:08 瀏覽:437
現在手機配置最高是什麼 發布:2025-01-11 04:30:37 瀏覽:549
學信網默認密碼是多少 發布:2025-01-11 04:25:45 瀏覽:530
jdbctemplate調用存儲過程 發布:2025-01-11 04:25:41 瀏覽:256
我的世界怎麼不用錢創建伺服器 發布:2025-01-11 04:25:39 瀏覽:283