SpringMVC框架中多数据源的配置问题、datasource

时间:2022-11-21 21:58:42


        多数据源,说白了,就是多数据库。因为我们配置数据源需要指定特定的数据库名称,如下,这是我们经常使用的配置数据源的XML文件内容中的一部分:


<!-- 配置数据源dataSource,连接MySQL数据库 、数据库: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就是我们的数据库名称。

如果我们需要配置多个数据源,我们就需要写多个bean,每个bean对应一个数据源。难点在于,如何控制多个数据源之间的切换。这里我们需要借助ThreadLocal类,这个类位于java.lang包下,首先说明ThreadLocal存放的值是线程内共享的,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递,这样处理后,能够优雅的解决一些实际问题,比如:

1,Hibernate中的OpenSessionInView,就是借助ThreadLocal保存Session对象;

2,数据库连接,借助ThreadLocal来传递Connection对象;


同样的,今天我们实现多数据源,也要借助ThreadLocal类,通过ThreadLocal类传递数据源的参数,我们这里传递的是bean的id,也就是SpringMVC中bean的名称,通过这个id,我们就可以调用相应的bean,这样就实现了不同数据源之间的切换。

首先要明确:
 * ThreadLocal类,是什么?首先ThreadLocal不是Thread,因为如果我们要创建
 * 一个线程,我们需要继承Thread类或者实现Runnable接口,ThreadLocal没有
 * 继承Thread类,也没有继承Runnable接口。

ThreadLocal的作用,就是将本地变量,也就是当前内存中的变量,与线程关联起来。


好的,废话不多说,先上代码:


	<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个数据源:datasource_test 和 datasource_learn_system,对应MySQL数据库中的2个不同的数据库;


然后我们需要写一个类:DynamicDataSource

<span style="font-size:14px;">import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/*
* 配置多数据源
*/

public class DynamicDataSource extends AbstractRoutingDataSource{

public static final String DATASOURCE_TEST = "datasource_test";

public static final String DATASOURCE_LEARN_SYSTEM = "datasource_learn_system";
//本地线程,获取当前正在执行的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>

好了,通过这些代码,我们就可以实现多数据源的切换了,接下来,我们只需要在执行数据库操作之前,切换数据源,就可以实现动态切换数据库的项目需求了。接下来看一下实例代码:


	//查询当前用户的所有上传图片
@Override
public boolean saveUploadPicture(Picture_of_user picture_of_user) {
//定义一个Boolean类型的flag,用来表示查询状态
boolean flag = false;

sql = "insert into picture_of_user(id,picture_name,picture_size,upload_date,picture_type,username) " +
"values(?,?,?,?,?,?);";

//切换数据源 datasource_test
//这段代码相当于,把String类型的参数 datasource_test 放在了保存到了本地线程的当前线程中,也就是当前正在执行的线程。
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()
});
//如果插入操作执行成功,则flag=true;否则flag=flase
if(i > 0){
//测试输出
System.out.println("i = " + i);
flag = true;
}
else{
//测试输出
System.out.println("i = " + i);
flag = false;
}
return flag;
}

我们只需要加上这一行代码,就可以实现数据源切换了:

DynamicDataSource.setCustomerType(DynamicDataSource.DATASOURCE_LEARN_SYSTEM);


这段代码做了什么呢?这段代码,把当前要选择的数据源,保存到ThreadLocal对象中。

接下来我们看一下源码:

<span style="font-size:14px;">package com.spring.dynamic_datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/*
* 配置多数据源
*/

public class DynamicDataSource extends AbstractRoutingDataSource{

public static final String DATASOURCE_TEST = "datasource_test";

public static final String DATASOURCE_LEARN_SYSTEM = "datasource_learn_system";
//本地线程,获取当前正在执行的currentThread
public static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

public static void setCustomerType(String customerType) {
//把当前请求的参数,存入当前线程,这个参数是我们定义的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("当前切换的数据源 = " + 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
*/

最后还是要重复说明:


 * ThreadLocal类,是什么?首先ThreadLocal不是Thread,因为如果我们要创建
 * 一个线程,我们需要继承Thread类或者实现Runnable接口,ThreadLocal没有
 * 继承Thread类,也没有继承Runnable接口。


ThreadLocal做了什么?ThreadLocal的作用,就是将本地变量,也就是当前内存中的变量,与线程关联起来,就像官方描述文档中说的:

 * 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个数据源,该如何操作呢?其实很简单,我们只需要再配置一个数据源就可以了。

思考:如果我们在项目中使用不同的数据库系统,比如MySQL、Oracle,该如何操作呢?同理,我们只需要对数据源的配置参数进行修改就可以了。


好了,关于多数据源的配置,就说到这里。接下来会给大家测试几个多数据源配置的实例。理论上可行的,还要经过实践检验。