基於注解的Spring多數據源配置和使用


前一段時間研究了一下spring多數據源的配置和使用,為了后期從多個數據源拉取數據定時進行數據分析和報表統計做准備。由於之前做過的項目都是單數據源的,沒有遇到這種場景,所以也一直沒有去了解過如何配置多數據源。
后來發現其實基於spring來配置和使用多數據源還是比較簡單的,因為spring框架已經預留了這樣的接口可以方便數據源的切換。
先看一下spring獲取數據源的源碼:

可以看到AbstractRoutingDataSource獲取數據源之前會先調用determineCurrentLookupKey方法查找當前的lookupKey,這個lookupKey就是數據源標識。
因此通過重寫這個查找數據源標識的方法就可以讓spring切換到指定的數據源了。
第一步:創建一個DynamicDataSource的類,繼承AbstractRoutingDataSource並重寫determineCurrentLookupKey方法,代碼如下:

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,簡化的配置如下:

 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進行操作了。

示例代碼如下:

 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 }

--------------------------------------------------------------------------------------華麗的分割線--------------------------------------------------------------------------------------------------

但是問題來了,如果每次切換數據源時都調用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的線程變量中:

 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層的所有方法:

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注解來指定數據源,則優先以方法上指定的為准。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM