spring有提供AbstractRoutingDataSource类来实现数据源的动态切换,用来实现读写分离也自然没什么问题了。

实现原理:扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。)

    

从AbstractRoutingDataSource的源码中:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

 

    我们可以看到,它继承了AbstractDataSource,而AbstractDataSource是javax.sql.DataSource的子类,我们可以分析下它的getConnection方法:

复制代码

public Connection getConnection() throws SQLException {  
    return determineTargetDataSource().getConnection();  
}  
   
public Connection getConnection(String username, String password) throws SQLException {  
     return determineTargetDataSource().getConnection(username, password);  
}

复制代码

 

    获取连接的方法中,重点是determineTargetDataSource()方法,看源码:

复制代码

/** 
     * Retrieve the current target DataSource. Determines the 
     * {@link #determineCurrentLookupKey() current lookup key}, performs 
     * a lookup in the {@link #setTargetDataSources targetDataSources} map, 
     * falls back to the specified 
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary. 
     * @see #determineCurrentLookupKey() 
     */  
    protected DataSource determineTargetDataSource() {  
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");  
        Object lookupKey = determineCurrentLookupKey();  
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);  
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {  
            dataSource = this.resolvedDefaultDataSource;  
        }  
        if (dataSource == null) {  
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");  
        }  
        return dataSource;  
    }

复制代码

 

    上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。


扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

实战:

  1. 扩展AbstractRoutingDataSource,传递不同数据源

  2. package org.scf.db;


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


  4. /**

  5.  * 扩展spring route data source

  6.  * @author scf

  7.  *

  8.  */

  9. public class DynamicDataSource extends AbstractRoutingDataSource{


  10. @Override

  11. protected Object determineCurrentLookupKey() {

  12. return CustomerContextHolder.getCustomerType();

  13. }


  14. }

  15. package org.scf.db;


  16. public class CustomerContextHolder {

  17. public static final String DATA_SOURCE_READ = "dataSourceRead";

  18.     

  19.     public static final String DATA_SOURCE_WRITE = "dataSourceWrite";

  20.      

  21.     private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();

  22.      

  23.     public static void setCustomerType(String customerType) {

  24.         contextHolder.set(customerType);

  25.     }

  26.     public static String getCustomerType() {

  27.         return contextHolder.get();

  28.     }

  29.     public static void clearCustomerType() {

  30.         contextHolder.remove();

  31.     }

  32. }

  33. 配置spring文件

  34. <?xml version="1.0" encoding="UTF-8"?>

  35. <beans xmlns="http://www.springframework.org/schema/beans"

  36.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"

  37.        xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"

  38.        xmlns:aop="http://www.springframework.org/schema/aop"

  39.        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

  40.        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd

  41.        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd   

  42.        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">


  43.     <!-- 定义受环境影响易变的变量 -->

  44.     <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

  45.         <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>

  46.         <property name="ignoreResourceNotFound" value="true"/>

  47.         <property name="locations">

  48.             <list>

  49.                 <!-- 标准配置 -->

  50.                 <value>classpath:application.properties</value>

  51.             </list>

  52.         </property>

  53.     </bean>

  54.     

  55.     <bean id="dataSourceWrite" class="org.apache.commons.dbcp.BasicDataSource">

  56.          <property name="driverClassName" value="${jdbc.driver.write}"/>

  57.         <property name="url" value="${jdbc.url.write}"/>

  58.         <property name="username" value="${jdbc.username.write}"/>

  59.         <property name="password" value="${jdbc.password.write}"></property>

  60.     </bean>

  61.     

  62.     <bean id="dataSourceRead" class="org.apache.commons.dbcp.BasicDataSource">

  63.         <property name="driverClassName" value="${jdbc.driver.read}"/>

  64.         <property name="url" value="${jdbc.url.read}"/>

  65.         <property name="username" value="${jdbc.username.read}"/>

  66.         <property name="password" value="${jdbc.password.read}"></property>

  67.     </bean>

  68.     

  69.     <!-- 动态数据源 -->

  70.     <bean id="dynamicDataSource" class="org.scf.db.DynamicDataSource">

  71.         <property name="targetDataSources">

  72.             <map key-type="java.lang.String">

  73.                 <entry value-ref="dataSourceRead" key="dataSourceRead"></entry>

  74.                 <entry value-ref="dataSourceWrite" key="dataSourceWrite"></entry>

  75.             </map>

  76.         </property>

  77.         <property name="defaultTargetDataSource" ref="dataSourceWrite">

  78.         </property>

  79.     </bean>


  80.     <!-- 启用aop -->

  81.     <aop:aspectj-autoproxy/>


  82.     <!-- 启用 annotation -->

  83.     <context:annotation-config/>


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

  85.         <property name="dataSource">

  86.             <!-- 默认是写数据源,程序中将其改为读数据源 -->

  87.             <ref bean="dynamicDataSource"/>

  88.         </property>

  89.     </bean>

  90.     

  91.     <bean id="db" class="org.scf.db.TemplateManager"></bean>


  92. </beans>  

    建立模板接口
  93. package org.scf.db;


  94. import java.util.List;

  95. import java.util.Map;


  96. import org.springframework.jdbc.core.JdbcTemplate;


  97. public interface DBManager {

  98. public JdbcTemplate getTemplate();


  99.    /**

  100.     * 插入/更新/删除

  101.     *

  102.     * @param sql

  103.     * @param args

  104.     * @return 本次更新影响的行数

  105.     */

  106.    public int update(String sql, Object[] args);

  107.    

  108.    /**

  109.     * 查询列表

  110.     *

  111.     * @param sql

  112.     * @param args

  113.     * @return 查询的数据列表

  114.     */

  115.    public List<Map<String, Object>> queryForList(String sql, Object[] args);

  116. }

  117. package org.scf.db;


  118. import java.util.List;

  119. import java.util.Map;


  120. import org.springframework.beans.factory.annotation.Autowired;

  121. import org.springframework.jdbc.core.JdbcTemplate;

  122. import org.springframework.stereotype.Component;


  123. @Component

  124. public class TemplateManager implements DBManager{

  125. @Autowired

  126.    private JdbcTemplate template;


  127.    public JdbcTemplate getTemplate() {

  128.        return template;

  129.    }


  130.    /**

  131.     * 插入/更新/删除

  132.     *

  133.     * @param sql

  134.     * @param args

  135.     * @return 本次更新影响的行数

  136.     */

  137.    @Override

  138.    public int update(String sql, Object[] args) {

  139.        int result = -1;

  140.        result = template.update(sql, args);

  141.        return result;

  142.    }

  143.    

  144.    /**

  145.     * 查询列表

  146.     *

  147.     * @param sql

  148.     * @param args

  149.     * @return 查询的数据列表

  150.     */

  151.    @Override

  152.    public List<Map<String, Object>> queryForList(String sql, Object[] args) {

  153.     CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_READ);//切换数据源,默认是写连接池,此处修改成读连接池

  154.        List<Map<String, Object>> result = null;

  155.        result = template.queryForList(sql, args);

  156.        return result;

  157.    }

  158. }

  159. 测试

  160. package org.scf.test;


  161. import static org.junit.Assert.*;


  162. import java.util.List;


  163. import org.junit.Test;

  164. import org.scf.db.DBManager;

  165. import org.springframework.context.ApplicationContext;

  166. import org.springframework.context.support.ClassPathXmlApplicationContext;



  167. public class RouteTestJ {

  168. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

  169. DBManager db  = (DBManager)context.getBean("db");

  170. @Test

  171. public void insertTest(){//写连接池

  172. String sql = "insert into scfTest values (4)";

  173. db.update(sql, null);

  174. }

  175. @Test

  176. public void searchTest(){//读连接池

  177. String sql = "select id from scfTest";

  178. List list = db.queryForList(sql, null);

  179. System.out.println(list.size());

  180. }

  181. }