前一段時間研究了一下spring多數據源的配置和使用,爲了後期從多個數據源拉取數據定時進行數據分析和報表統計作準備。因爲以前作過的項目都是單數據源的,沒有遇到這種場景,因此也一直沒有去了解過如何配置多數據源。
後來發現其實基於spring來配置和使用多數據源仍是比較簡單的,由於spring框架已經預留了這樣的接口能夠方便數據源的切換。
先看一下spring獲取數據源的源碼:java
能夠看到AbstractRoutingDataSource獲取數據源以前會先調用determineCurrentLookupKey方法查找當前的lookupKey,這個lookupKey就是數據源標識。
所以經過重寫這個查找數據源標識的方法就可讓spring切換到指定的數據源了。
第一步:建立一個DynamicDataSource的類,繼承AbstractRoutingDataSource並重寫determineCurrentLookupKey方法,代碼以下:spring
1 public class DynamicDataSource extends AbstractRoutingDataSource { 2
3 @Override 4 protected Object determineCurrentLookupKey() { 5 // 從自定義的位置獲取數據源標識
6 return DynamicDataSourceHolder.getDataSource(); 7 } 8
9 }
第二步:建立DynamicDataSourceHolder用於持有當前線程中使用的數據源標識,代碼以下:數據庫
1 public class DynamicDataSourceHolder { 2 /**
3 * 注意:數據源標識保存在線程變量中,避免多線程操做數據源時互相干擾 4 */
5 private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>(); 6
7 public static String getDataSource() { 8 return THREAD_DATA_SOURCE.get(); 9 } 10
11 public static void setDataSource(String dataSource) { 12 THREAD_DATA_SOURCE.set(dataSource); 13 } 14
15 public static void clearDataSource() { 16 THREAD_DATA_SOURCE.remove(); 17 } 18
19 }
第三步:配置多個數據源和第一步裏建立的DynamicDataSource的bean,簡化的配置以下:express
1 <!--建立數據源1,鏈接數據庫db1 -->
2 <bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
3 <property name="driverClassName" value="${db1.driver}" />
4 <property name="url" value="${db1.url}" />
5 <property name="username" value="${db1.username}" />
6 <property name="password" value="${db1.password}" />
7 </bean>
8 <!--建立數據源2,鏈接數據庫db2 -->
9 <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
10 <property name="driverClassName" value="${db2.driver}" />
11 <property name="url" value="${db2.url}" />
12 <property name="username" value="${db2.username}" />
13 <property name="password" value="${db2.password}" />
14 </bean>
15 <!--建立數據源3,鏈接數據庫db3 -->
16 <bean id="dataSource3" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
17 <property name="driverClassName" value="${db3.driver}" />
18 <property name="url" value="${db3.url}" />
19 <property name="username" value="${db3.username}" />
20 <property name="password" value="${db3.password}" />
21 </bean>
22
23 <bean id="dynamicDataSource" class="com.test.context.datasource.DynamicDataSource">
24 <property name="targetDataSources">
25 <map key-type="java.lang.String">
26 <!-- 指定lookupKey和與之對應的數據源 -->
27 <entry key="dataSource1" value-ref="dataSource1"></entry>
28 <entry key="dataSource2" value-ref="dataSource2"></entry>
29 <entry key="dataSource3 " value-ref="dataSource3"></entry>
30 </map>
31 </property>
32 <!-- 這裏能夠指定默認的數據源 -->
33 <property name="defaultTargetDataSource" ref="dataSource1" />
34 </bean>
到這裏已經可使用多數據源了,在操做數據庫以前只要DynamicDataSourceHolder.setDataSource("dataSource2")便可切換到數據源2並對數據庫db2進行操做了。apache
示例代碼以下:多線程
1 @Service 2 public class DataServiceImpl implements DataService { 3 @Autowired 4 private DataMapper dataMapper; 5
6 @Override 7 public List<Map<String, Object>> getList1() { 8 // 沒有指定,則默認使用數據源1
9 return dataMapper.getList1(); 10 } 11
12 @Override 13 public List<Map<String, Object>> getList2() { 14 // 指定切換到數據源2
15 DynamicDataSourceHolder.setDataSource("dataSource2"); 16 return dataMapper.getList2(); 17 } 18
19 @Override 20 public List<Map<String, Object>> getList3() { 21 // 指定切換到數據源3
22 DynamicDataSourceHolder.setDataSource("dataSource3"); 23 return dataMapper.getList3(); 24 } 25 }
--------------------------------------------------------------------------------------華麗的分割線--------------------------------------------------------------------------------------------------app
可是問題來了,若是每次切換數據源時都調用DynamicDataSourceHolder.setDataSource("xxx")就顯得十分繁瑣了,並且代碼量大了很容易會遺漏,後期維護起來也比較麻煩。能不能直接經過註解的方式指定須要訪問的數據源呢,好比在dao層使用@DataSource("xxx")就指定訪問數據源xxx?固然能夠!前提是,再加一點額外的配置^_^。
首先,咱們得定義一個名爲DataSource的註解,代碼以下:框架
1 @Target({ TYPE, METHOD }) 2 @Retention(RUNTIME) 3 public @interface DataSource { 4 String value(); 5 }
而後,定義AOP切面以便攔截全部帶有註解@DataSource的方法,取出註解的值做爲數據源標識放到DynamicDataSourceHolder的線程變量中:ide
1 public class DataSourceAspect { 2
3 /**
4 * 攔截目標方法,獲取由@DataSource指定的數據源標識,設置到線程存儲中以便切換數據源 5 * 6 * @param point 7 * @throws Exception 8 */
9 public void intercept(JoinPoint point) throws Exception { 10 Class<?> target = point.getTarget().getClass(); 11 MethodSignature signature = (MethodSignature) point.getSignature(); 12 // 默認使用目標類型的註解,若是沒有則使用其實現接口的註解
13 for (Class<?> clazz : target.getInterfaces()) { 14 resolveDataSource(clazz, signature.getMethod()); 15 } 16 resolveDataSource(target, signature.getMethod()); 17 } 18
19 /**
20 * 提取目標對象方法註解和類型註解中的數據源標識 21 * 22 * @param clazz 23 * @param method 24 */
25 private void resolveDataSource(Class<?> clazz, Method method) { 26 try { 27 Class<?>[] types = method.getParameterTypes(); 28 // 默認使用類型註解
29 if (clazz.isAnnotationPresent(DataSource.class)) { 30 DataSource source = clazz.getAnnotation(DataSource.class); 31 DynamicDataSourceHolder.setDataSource(source.value()); 32 } 33 // 方法註解能夠覆蓋類型註解
34 Method m = clazz.getMethod(method.getName(), types); 35 if (m != null && m.isAnnotationPresent(DataSource.class)) { 36 DataSource source = m.getAnnotation(DataSource.class); 37 DynamicDataSourceHolder.setDataSource(source.value()); 38 } 39 } catch (Exception e) { 40 System.out.println(clazz + ":" + e.getMessage()); 41 } 42 } 43
44 }
最後在spring配置文件中配置攔截規則就能夠了,好比攔截service層或者dao層的全部方法:url
1 <bean id="dataSourceAspect" class="com.test.context.datasource.DataSourceAspect" />
2 <aop:config>
3 <aop:aspect ref="dataSourceAspect">
4 <!-- 攔截全部service方法 -->
5 <aop:pointcut id="dataSourcePointcut" expression="execution(* com.test.*.dao.*.*(..))"/>
6 <aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
7 </aop:aspect>
8 </aop:config>
9 </bean>
OK,這樣就能夠直接在類或者方法上使用註解@DataSource來指定數據源,不須要每次都手動設置了。
示例代碼以下:
1 @Service 2 // 默認DataServiceImpl下的全部方法均訪問數據源1
3 @DataSource("dataSource1") 4 public class DataServiceImpl implements DataService { 5 @Autowired 6 private DataMapper dataMapper; 7
8 @Override 9 public List<Map<String, Object>> getList1() { 10 // 不指定,則默認使用數據源1
11 return dataMapper.getList1(); 12 } 13
14 @Override 15 // 覆蓋類上指定的,使用數據源2
16 @DataSource("dataSource2") 17 public List<Map<String, Object>> getList2() { 18 return dataMapper.getList2(); 19 } 20
21 @Override 22 // 覆蓋類上指定的,使用數據源3
23 @DataSource("dataSource3") 24 public List<Map<String, Object>> getList3() { 25 return dataMapper.getList3(); 26 } 27 }
提示:註解@DataSource既能夠加在方法上,也能夠加在接口或者接口的實現類上,優先級別:方法>實現類>接口。也就是說若是接口、接口實現類以及方法上分別加了@DataSource註解來指定數據源,則優先以方法上指定的爲準。