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

来源:互联网 发布:产品成本分析软件 编辑:程序博客网 时间:2024/05/16 15:48


        多数据源,说白了,就是多数据库。因为我们配置数据源需要指定特定的数据库名称,如下,这是我们经常使用的配置数据源的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";//本地线程,获取当前正在执行的currentThreadpublic 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();        }@Overrideprotected 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>

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


//查询当前用户的所有上传图片@Overridepublic 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=flaseif(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";//本地线程,获取当前正在执行的currentThreadpublic 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();        }@Overrideprotected 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,该如何操作呢?同理,我们只需要对数据源的配置参数进行修改就可以了。


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


2 0
原创粉丝点击