当项目上遇到读写分离,分表分库,版本升级数据导出等多数据源需求的时候,如何实现动态切换数据源? 
 
原理 Spring提供了AbstractRoutingDataSource 
1 2 3 4 5 6 7 8 @Override public  Connection getConnection ()  throws  SQLException 	return  determineTargetDataSource().getConnection(); } @Override public  Connection getConnection (String username, String password)  throws  SQLException 	return  determineTargetDataSource().getConnection(username, password); } 
实现interface DataSource 的getConnection()方法都是使用determineTargetDataSource()方法返回的connection,再观察这个方法发现:
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  * 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; } 
返回的key是通过determineCurrentLookupKey()这个抽象方法获取的,说明只要重写这个方法返回需要使用的数据源key即可。
思考 
但是如何在程序使用过程中自动切换数据源呢?如何方便的自动标记此时需要使用哪个数据源?
 
实现 spring中的aop功能,可以以切入的方式,在系统使用的过程中动态切换数据源。我们可以对service进行aop实现。但是如何准确方便的标记那些service使用A数据源,哪些service使用B数据源呢? 此时我们可以使用java的annotation 
下面是具体的代码实现:
多数据源配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <util:properties  id ="dbConfig"  location ="classpath:/config/jdbc.properties" > </util:properties > <beans:bean  id ="testDataSorce"  class ="org.apache.commons.dbcp.BasicDataSource"            lazy-init ="default"  autowire ="default" > 	<beans:property  name ="driverClassName"  value ="#{dbConfig['driver']}" > </beans:property >  	<beans:property  name ="url"  value ="#{dbConfig['test.url']}" > </beans:property >  	<beans:property  name ="username"  value ="#{dbConfig['test.user']}" > </beans:property >  	<beans:property  name ="password"  value ="#{dbConfig['test.password']}" > </beans:property >  	<beans:property  name ="maxWait"  value ="#{dbConfig['maxwait']}" > </beans:property >  </beans:bean > <beans:bean  id ="devDataSorce"  class ="org.apache.commons.dbcp.BasicDataSource"                  lazy-init ="default"  autowire ="default" > 	<beans:property  name ="driverClassName"  value ="#{dbConfig['driver']}" > </beans:property >  	<beans:property  name ="url"  value ="#{dbConfig['dev.url']}" > </beans:property >  	<beans:property  name ="username"  value ="#{dbConfig['dev.user']}" > </beans:property >  	<beans:property  name ="password"  value ="#{dbConfig['dev.password']}" > </beans:property >  	<beans:property  name ="maxWait"  value ="#{dbConfig['maxwait']}" > </beans:property >  </beans:bean > <beans:bean  id ="dataSource"  class ="com.yyy.web.mess.datasource.DynamicDataSource"  lazy-init ="default"                  autowire ="default" > 	 <beans:property  name ="targetDataSources" >           <beans:map  key-type ="java.lang.String" >               <beans:entry  key ="testDataSorce"  value-ref ="testDataSorce" />               <beans:entry  key ="devDataSorce"  value-ref ="devDataSorce" />           </beans:map >      </beans:property >      <beans:property  name ="defaultTargetDataSource"  ref ="testDataSorce" />      <beans:property  name ="defaultDataSourceKey"  value ="testDataSorce" />  </beans:bean > 
此处可以配置多个,且不同类型的数据源,然后需要将数据源加入到dataSource这个bean下的targetDataSources中。
实现 com.yyy.web.mess.datasource.DynamicDataSource继承自前面所提到的AbstractRoutingDataSource,重写determineCurrentLookupKey方法,返回需要使用的数据源key(上面配置的bean id),具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package  com.yyy.web.mess.datasource;import  org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; * Created by 程祥 on 15/10/21.  * Function:  */ public  class  DynamicDataSource  extends  AbstractRoutingDataSource     private  String defaultDataSourceKey;     @Override      protected  Object determineCurrentLookupKey ()           String dataSource = DataSourceSwitcher.getDataSource();         if (dataSource != null ){             return  dataSource;         }         return  defaultDataSourceKey;     }     public  String getDefaultDataSourceKey ()           return  defaultDataSourceKey;     }     public  void  setDefaultDataSourceKey (String defaultDataSourceKey)           this .defaultDataSourceKey = defaultDataSourceKey;     } } 
使用DataSourceSwitcher单开一个线程,记录当前使用的数据源。
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package  com.yyy.web.mess.datasource;import  org.apache.commons.lang.StringUtils; * Created by 程祥 on 15/10/21.  * Function:  */ public  class  DataSourceSwitcher           @SuppressWarnings ("rawtypes" )     private  static  final  ThreadLocal contextHolder = new  ThreadLocal();     @SuppressWarnings ("unchecked" )     public  static  void  setDataSource (String dataSource)           if (StringUtils.isNotEmpty(dataSource)){         		contextHolder.set(dataSource);         }     }          public  static  String getDataSource ()           String currDataSource = (String) contextHolder.get();         return  currDataSource;     }     public  static  void  clearDataSource ()           contextHolder.remove();     } } 
AOP切入 添加一个advice,在方法执行前设置需要使用的数据源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  class  AnnotationMultipleDataSourceAdvice  implements  MethodBeforeAdvice , AfterReturningAdvice ,ThrowsAdvice     public  void  before (Method method, Object[] args, Object target)  throws  Throwable      	         DataSource dataSource = method.getAnnotation(DataSource.class);         String datasourceKey=dataSource!=null  ? dataSource.value():null ;         DataSourceSwitcher.setDataSource(datasourceKey);     } 	     public  void  afterReturning (Object arg0, Method method, Object[] args, Object target)  throws  Throwable          DataSourceSwitcher.clearDataSource();     } 	     public  void  afterThrowing (Method method, Object[] args, Object target, Exception ex)  throws  Throwable          DataSourceSwitcher.clearDataSource();     } } 
annotation的实现
1 2 3 4 5 6 @Inherited @Retention (RetentionPolicy.RUNTIME)@Target ({ElementType.METHOD,ElementType.PACKAGE})public  @interface  DataSource {    String value ()  ; } 
aop配置文件设置:
1 2 3 4 5 6 <aop:config >     <aop:pointcut  id ="serviceMethods"  expression ="execution(* com.yyy.web.mess.service..*(..))" />      <aop:advisor  advice-ref ="annotationMultipleDataSourceAdvice"  pointcut-ref ="serviceMethods" />  </aop:config > <beans:bean  id ="annotationMultipleDataSourceAdvice"        class ="com.yyy.web.mess.datasource.impl.AnnotationMultipleDataSourceAdvice" > </beans:bean > 
至此,所有配置及实现均已完成。
使用 使用的时候只需要在对用的service方法前加上对应的key,mybatis在请求数据源的时候就会根据当前数据源去执行对应操作。如果不设置,则使用的是默认数据源~
1 2 3 4 5 6 7 8 9 10 11  * Created by 程祥 on 15/10/21.  * Function:  */ public  interface  OrderDevService      @DataSource ("devDataSorce" )     int  insertDatas2Dev (List<Order> orders)  throws  Exception     @DataSource ("devDataSorce" )     List<Order> selectTest ()  throws  Exception ; } 
总结 通过annotation能够方便且清晰的告知当前需要使用哪个数据源,使用aop通过解析annotation的value,动态的切换数据源。这样在需要多数据源系统中能够方便高效的编写业务逻辑代码。