來源:春秋戰(zhàn)國程序猿 發(fā)布時間:2018-11-14 11:22:19 閱讀量:946
多數(shù)據(jù)源,說白了,就是多數(shù)據(jù)庫。因為我們配置數(shù)據(jù)源需要指定特定的數(shù)據(jù)庫名稱,如下,這是我們經(jīng)常使用的配置數(shù)據(jù)源的XML文件內(nèi)容中的一部分:
<!-- 配置數(shù)據(jù)源dataSource,連接MySQL數(shù)據(jù)庫 、數(shù)據(jù)庫:learn_system -->
<!--
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="com.mysql.jdbc.Driver">
</property>
<property name="url"
value="jdbc:mysql://localhost:3306/learn_system">
</property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
-->
這里value="jdbc:mysql://localhost:3306/learn_system"的learn_system就是我們的數(shù)據(jù)庫名稱。
如果我們需要配置多個數(shù)據(jù)源,我們就需要寫多個bean,每個bean對應(yīng)一個數(shù)據(jù)源。難點在于,如何控制多個數(shù)據(jù)源之間的切換。這里我們需要借助ThreadLocal類,這個類位于java.lang包下,首先說明ThreadLocal存放的值是線程內(nèi)共享的,線程間互斥的,主要用于線程內(nèi)共享一些數(shù)據(jù),避免通過參數(shù)來傳遞,這樣處理后,能夠優(yōu)雅的解決一些實際問題,比如:
1,Hibernate中的OpenSessionInView,就是借助ThreadLocal保存Session對象;
2,數(shù)據(jù)庫連接,借助ThreadLocal來傳遞Connection對象;
同樣的,今天我們實現(xiàn)多數(shù)據(jù)源,也要借助ThreadLocal類,通過ThreadLocal類傳遞數(shù)據(jù)源的參數(shù),我們這里傳遞的是bean的id,也就是SpringMVC中bean的名稱,通過這個id,我們就可以調(diào)用相應(yīng)的bean,這樣就實現(xiàn)了不同數(shù)據(jù)源之間的切換。
首先要明確:
* ThreadLocal類,是什么?首先ThreadLocal不是Thread,因為如果我們要創(chuàng)建
* 一個線程,我們需要繼承Thread類或者實現(xiàn)Runnable接口,ThreadLocal沒有
* 繼承Thread類,也沒有繼承Runnable接口。
ThreadLocal的作用,就是將本地變量,也就是當前內(nèi)存中的變量,與線程關(guān)聯(lián)起來。
好的,廢話不多說,先上代碼:
<bean id="datasource_test" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="com.mysql.jdbc.Driver">
</property>
<property name="url"
value="jdbc:mysql://localhost:3306/test">
</property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="datasource_learn_system" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="com.mysql.jdbc.Driver">
</property>
<property name="url"
value="jdbc:mysql://localhost:3306/learn_system">
</property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
上面配置了2個數(shù)據(jù)源:datasource_test 和 datasource_learn_system,對應(yīng)MySQL數(shù)據(jù)庫中的2個不同的數(shù)據(jù)庫;
然后我們需要寫一個類:DynamicDataSource
<span style="font-size:14px;">import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/*
* 配置多數(shù)據(jù)源
*/
public class DynamicDataSource extends AbstractRoutingDataSource{
public static final String DATASOURCE_TEST = "datasource_test";
public static final String DATASOURCE_LEARN_SYSTEM = "datasource_learn_system";
//本地線程,獲取當前正在執(zhí)行的currentThread
public static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
return contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
@Override
protected Object determineCurrentLookupKey() {
return getCustomerType();
}
}</span>
接下來我們要在配置文件中進行配置,
<bean id="dataSource" class="com.spring.dynamic_datasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="datasource_test" key="datasource_test"></entry>
<entry value-ref="datasource_learn_system" key="datasource_learn_system"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="datasource_learn_system"></property>
</bean>
好了,通過這些代碼,我們就可以實現(xiàn)多數(shù)據(jù)源的切換了,接下來,我們只需要在執(zhí)行數(shù)據(jù)庫操作之前,切換數(shù)據(jù)源,就可以實現(xiàn)動態(tài)切換數(shù)據(jù)庫的項目需求了。接下來看一下實例代碼:
//查詢當前用戶的所有上傳圖片
@Override
public boolean saveUploadPicture(Picture_of_user picture_of_user) {
//定義一個Boolean類型的flag,用來表示查詢狀態(tài)
boolean flag = false;
sql = "insert into picture_of_user(id,picture_name,picture_size,upload_date,picture_type,username) " +
"values(?,?,?,?,?,?);";
//切換數(shù)據(jù)源 datasource_test
//這段代碼相當于,把String類型的參數(shù) datasource_test 放在了保存到了本地線程的當前線程中,也就是當前正在執(zhí)行的線程。
DynamicDataSource.setCustomerType(DynamicDataSource.DATASOURCE_LEARN_SYSTEM);
int i = this.getJdbcTemplate().update(sql, new Object[]{
null,
picture_of_user.getPicture_name(),
picture_of_user.getPicture_size(),
picture_of_user.getUpload_date(),
picture_of_user.getPicture_type(),
picture_of_user.getUsername()
});
//如果插入操作執(zhí)行成功,則flag=true;否則flag=flase
if(i > 0){
//測試輸出
System.out.println("i = " + i);
flag = true;
}
else{
//測試輸出
System.out.println("i = " + i);
flag = false;
}
return flag;
}
我們只需要加上這一行代碼,就可以實現(xiàn)數(shù)據(jù)源切換了:
DynamicDataSource.setCustomerType(DynamicDataSource.DATASOURCE_LEARN_SYSTEM);
這段代碼做了什么呢?這段代碼,把當前要選擇的數(shù)據(jù)源,保存到ThreadLocal對象中。
接下來我們看一下源碼:
<span style="font-size:14px;">package com.spring.dynamic_datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/*
* 配置多數(shù)據(jù)源
*/
public class DynamicDataSource extends AbstractRoutingDataSource{
public static final String DATASOURCE_TEST = "datasource_test";
public static final String DATASOURCE_LEARN_SYSTEM = "datasource_learn_system";
//本地線程,獲取當前正在執(zhí)行的currentThread
public static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
//把當前請求的參數(shù),存入當前線程,這個參數(shù)是我們定義的DATASOURCE_TEST 或者 DATASOURCE_LEARN_SYSTEM
//我們來看一下源碼:
//public void set(T value) {
// Thread t = Thread.currentThread();
// ThreadLocalMap map = getMap(t);
// if (map != null)
// map.set(this, value);
// else
// createMap(t, value);
//這段代碼的官方說明如下
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
//測試輸出
System.out.println("當前切換的數(shù)據(jù)源 = " + customerType);
contextHolder.set(customerType);
}
public static String getCustomerType() {
//官方文檔如下:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
//public T get() {
//Thread t = Thread.currentThread();
//ThreadLocalMap map = getMap(t);
//if (map != null) {
//ThreadLocalMap.Entry e = map.getEntry(this);
//if (e != null)
// return (T)e.value;
//}
// return setInitialValue();
//}
return contextHolder.get();
}
public static void clearCustomerType() {
/**
* Removes the current thread's value for this thread-local
* variable. If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim. This may result in multiple invocations of the
* <tt>initialValue</tt> method in the current thread.
*
* @since 1.5
*/
//源代碼如下:
//public void remove() {
// ThreadLocalMap m = getMap(Thread.currentThread());
//if (m != null)
// m.remove(this);
//}
contextHolder.remove();
}
@Override
protected Object determineCurrentLookupKey() {
return getCustomerType();
}
}</span>
public class ThreadLocal<T> 類沒有繼承其他類,只是間接繼承了Object類,我們來看一下官方對這個類的使用說明:
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* <tt>get</tt> or <tt>set</tt> method) has its own, independently initialized
* copy of the variable. <tt>ThreadLocal</tt> instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*
* <p>For example, the class below generates unique identifiers local to each
* thread.
* A thread's id is
* assigned the first time it invokes <tt>UniqueThreadIdGenerator.getCurrentThreadId()</tt> and remains unchanged on subsequent calls.
* <pre>
* import java.util.concurrent.atomic.AtomicInteger;
*
* public class UniqueThreadIdGenerator {
*
* private static final AtomicInteger uniqueId = new AtomicInteger(0);
*
* private static final ThreadLocal < Integer > uniqueNum =
* new ThreadLocal < Integer > () {
* @Override protected Integer initialValue() {
* return uniqueId.getAndIncrement();
* }
* };
*
* public static int getCurrentThreadId() {
* return uniqueId.get();
* }
* } // UniqueThreadIdGenerator
* </pre>
* <p>Each thread holds an implicit reference to its copy of a thread-local
* variable as long as the thread is alive and the <tt>ThreadLocal</tt>
* instance is accessible; after a thread goes away, all of its copies of
* thread-local instances are subject to garbage collection (unless other
* references to these copies exist).
*
* @author Josh Bloch and Doug Lea
* @version 1.42, 06/23/06
* @since 1.2
*/
最后還是要重復(fù)說明:
* ThreadLocal類,是什么?首先ThreadLocal不是Thread,因為如果我們要創(chuàng)建
* 一個線程,我們需要繼承Thread類或者實現(xiàn)Runnable接口,ThreadLocal沒有
* 繼承Thread類,也沒有繼承Runnable接口。
ThreadLocal做了什么?ThreadLocal的作用,就是將本地變量,也就是當前內(nèi)存中的變量,與線程關(guān)聯(lián)起來,就像官方描述文檔中說的:
* ThreadLocal provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one has its own, independently initialized
* copy of the variable. ThreadLocal instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
思考:如果我們需要配置3個數(shù)據(jù)源,該如何操作呢?其實很簡單,我們只需要再配置一個數(shù)據(jù)源就可以了。
思考:如果我們在項目中使用不同的數(shù)據(jù)庫系統(tǒng),比如MySQL、Oracle,該如何操作呢?同理,我們只需要對數(shù)據(jù)源的配置參數(shù)進行修改就可以了。
好了,關(guān)于多數(shù)據(jù)源的配置,就說到這里。接下來會給大家測試幾個多數(shù)據(jù)源配置的實例。理論上可行的,還要經(jīng)過實踐檢驗。
---------------------