<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
一般在設定資料來源是都會使用xml的方式注入,key-value在properties中管理;spring4.X已有著比較完善的註解來替換xml的設定方式。
通常我們使用xml設定資料來源,使用SpEL獲取properties中的設定。
applicationContext.xml 中設定 dataSource 及 PreferencesPlaceholderConfigurer,使用 PropertyPlaceholderConfigurer進行Bean屬性替換
<bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>classpath:/jdbc.properties</value> </list> </property> <property name="fileEncoding" value="utf-8"/> </bean> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer"> <property name="properties" ref="configProperties" /> </bean> <!-- 使用proxool連線池的資料來源, --> <bean id="dataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource"> <!-- 資料來源的別名 --> <property name="alias" value="${proxool.alias}" /> <!-- 驅動 --> <property name="driver" value="${proxool.driver}" /> <!-- 連結URL --> <property name="driverUrl" value="${proxool.driverUrl}" /> <!-- 使用者名稱--> <property name="user" value="${proxool.user}" /> <!-- 密碼 --> <property name="password" value="${proxool.password}" /> <!-- 最大連結數--> <property name="maximumConnectionCount" value="${proxool.maximumConnectionCount}" /> <!-- 最小連結數 --> <property name="minimumConnectionCount" value="${proxool.minimumConnectionCount}" /> <!-- ...(略) --> </bean>
jdbc.properties
proxool.alias=mySql proxool.driver=com.mysql.jdbc.Driver proxool.driverUrl=jdbc:mysql://localhost:3306/test?characterEncoding=utf8 proxool.user=root proxool.password=root proxool.maximumActiveTime=1200 proxool.maximumConnectionCount=50 #...
DataSourceConfiguration類是資料來源的javaBean設定方式,@Configuratio註解當前類,
spring啟動時會掃描被@Configuratio註解的類,注入當前類中設定的方法bean;
當然別忘了啟用註解掃描:
<context:annotation-config/> <context:component-scan base-package="com.XXX.test.dateSource"></context:component-scan>
@value中可以直接使用SpEL,獲取properties設定,成員變數也不需要getter、setter,不過還是有一個前提,需要設定xml:
<bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>classpath:/jdbc.properties</value> </list> </property> <property name="fileEncoding" value="utf-8"/> </bean> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer"> <property name="properties" ref="configProperties" /> </bean>
@Bean註解:spring掃面當前類時,注入每個有@Bean註解的方法的返回值Bean, name屬性預設為返回值類類名首字母小寫,這裡自己設定name。
package com.XXX.test.dateSource; import org.logicalcobwebs.proxool.ProxoolDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuratio public class DataSourceConfiguration{ @Value("${proxool.alias}") private String alias; @Value("${proxool.driver}") private String driver; @Value("${proxool.driverUrl}") private String driverUrl; @Value("${proxool.user}") private String user; @Value("${proxool.password}") private String password; //... @Bean(name="dataSource") public ProxoolDataSource dataSource(){ ProxoolDataSource proxoolDataSource = new ProxoolDataSource(); proxoolDataSource.setDriver(driver); proxoolDataSource.setDriverUrl(driverUrl); proxoolDataSource.setUser(user); proxoolDataSource.setPassword(password); //... return proxoolDataSource; } }
這時dataSource已被注入,使用時可註解注入,如下:
@Autowired private ProxoolDataSource dataSource;
@PropertySource註解當前類,引數為對應的組態檔路徑,這種方式載入組態檔,可不用在xml中設定PropertiesFactoryBean引入jdbc.properties,使用時方便得多,DataSourceConfiguration不再需要成員變數,取而代之的是需要注入一個Environment環境設定,使用env.getProperty(key)獲取資料:
package com.XXX.test.dateSource; import org.logicalcobwebs.proxool.ProxoolDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; @Configuratio @PropertySource("classpath:/jdbc.properties") public class DataSourceConfiguration{ @Autowired private Environment env; @Bean(name="dataSource") public ProxoolDataSource dataSource(){ ProxoolDataSource proxoolDataSource = new ProxoolDataSource(); proxoolDataSource.setDriver(env.getProperty("proxool.alias")); proxoolDataSource.setDriverUrl(env.getProperty("proxool.driver")); proxoolDataSource.setUser(env.getProperty("proxool.user")); proxoolDataSource.setPassword(env.getProperty("proxool.password")); //... return proxoolDataSource; } }
這裡主要是說明註解的用法,所以沒有具體體現資料來源全部引數的設定。對於有強迫症的來說若專案中所有bean都使用註解,幾乎不太希望僅dataSource用xml類設定,換成類的方式類設定強迫感就消失了!
前一段時間研究了一下spring多資料來源的設定和使用,為了後期從多個資料來源拉取資料定時進行資料分析和報表統計做準備。由於之前做過的專案都是單資料來源的,沒有遇到這種場景,所以也一直沒有去了解過如何設定多資料來源。
後來發現其實基於spring來設定和使用多資料來源還是比較簡單的,因為spring框架已經預留了這樣的介面可以方便資料來源的切換。
可以看到AbstractRoutingDataSource獲取資料來源之前會先呼叫determineCurrentLookupKey方法查詢當前的lookupKey,這個lookupKey就是資料來源標識。
因此通過重寫這個查詢資料來源標識的方法就可以讓spring切換到指定的資料來源了。
繼承AbstractRoutingDataSource並重寫determineCurrentLookupKey方法,程式碼如下:
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { // 從自定義的位置獲取資料來源標識 return DynamicDataSourceHolder.getDataSource(); } }
用於持有當前執行緒中使用的資料來源標識,程式碼如下:
public class DynamicDataSourceHolder { /** * 注意:資料來源標識儲存線上程變數中,避免多執行緒運算元據源時互相干擾 */ private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>(); public static String getDataSource() { return THREAD_DATA_SOURCE.get(); } public static void setDataSource(String dataSource) { THREAD_DATA_SOURCE.set(dataSource); } public static void clearDataSource() { THREAD_DATA_SOURCE.remove(); } }
和第一步裡建立的DynamicDataSource的bean,簡化的設定如下:
<!--建立資料來源1,連線資料庫db1 --> <bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${db1.driver}" /> <property name="url" value="${db1.url}" /> <property name="username" value="${db1.username}" /> <property name="password" value="${db1.password}" /> </bean> <!--建立資料來源2,連線資料庫db2 --> <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${db2.driver}" /> <property name="url" value="${db2.url}" /> <property name="username" value="${db2.username}" /> <property name="password" value="${db2.password}" /> </bean> <!--建立資料來源3,連線資料庫db3 --> <bean id="dataSource3" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${db3.driver}" /> <property name="url" value="${db3.url}" /> <property name="username" value="${db3.username}" /> <property name="password" value="${db3.password}" /> </bean> <bean id="dynamicDataSource" class="com.test.context.datasource.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- 指定lookupKey和與之對應的資料來源 --> <entry key="dataSource1" value-ref="dataSource1"></entry> <entry key="dataSource2" value-ref="dataSource2"></entry> <entry key="dataSource3 " value-ref="dataSource3"></entry> </map> </property> <!-- 這裡可以指定預設的資料來源 --> <property name="defaultTargetDataSource" ref="dataSource1" /> </bean>
到這裡已經可以使用多資料來源了,在運算元據庫之前只要DynamicDataSourceHolder.setDataSource("dataSource2")即可切換到資料來源2並對資料庫db2進行操作了。
範例程式碼如下:
@Service public class DataServiceImpl implements DataService { @Autowired private DataMapper dataMapper; @Override public List<Map<String, Object>> getList1() { // 沒有指定,則預設使用資料來源1 return dataMapper.getList1(); } @Override public List<Map<String, Object>> getList2() { // 指定切換到資料來源2 DynamicDataSourceHolder.setDataSource("dataSource2"); return dataMapper.getList2(); } @Override public List<Map<String, Object>> getList3() { // 指定切換到資料來源3 DynamicDataSourceHolder.setDataSource("dataSource3"); return dataMapper.getList3(); } }
----------------------------華麗的分割線----------------------------
但是問題來了,如果每次切換資料來源時都呼叫DynamicDataSourceHolder.setDataSource("xxx")就顯得十分繁瑣了,而且程式碼量大了很容易會遺漏,後期維護起來也比較麻煩。能不能直接通過註解的方式指定需要存取的資料來源呢,比如在dao層使用@DataSource("xxx")就指定存取資料來源xxx?當然可以!前提是,再加一點額外的設定^_^。
首先,我們得定義一個名為DataSource的註解,程式碼如下:
@Target({ TYPE, METHOD }) @Retention(RUNTIME) public @interface DataSource { String value(); }
然後,定義AOP切面以便攔截所有帶有註解@DataSource的方法,取出註解的值作為資料來源標識放到DynamicDataSourceHolder的執行緒變數中:
public class DataSourceAspect { /** * 攔截目標方法,獲取由@DataSource指定的資料來源標識,設定到執行緒儲存中以便切換資料來源 * * @param point * @throws Exception */ public void intercept(JoinPoint point) throws Exception { Class<?> target = point.getTarget().getClass(); MethodSignature signature = (MethodSignature) point.getSignature(); // 預設使用目標型別的註解,如果沒有則使用其實現介面的註解 for (Class<?> clazz : target.getInterfaces()) { resolveDataSource(clazz, signature.getMethod()); } resolveDataSource(target, signature.getMethod()); } /** * 提取目標物件方法註解和型別註解中的資料來源標識 * * @param clazz * @param method */ private void resolveDataSource(Class<?> clazz, Method method) { try { Class<?>[] types = method.getParameterTypes(); // 預設使用型別註解 if (clazz.isAnnotationPresent(DataSource.class)) { DataSource source = clazz.getAnnotation(DataSource.class); DynamicDataSourceHolder.setDataSource(source.value()); } // 方法註解可以覆蓋型別註解 Method m = clazz.getMethod(method.getName(), types); if (m != null && m.isAnnotationPresent(DataSource.class)) { DataSource source = m.getAnnotation(DataSource.class); DynamicDataSourceHolder.setDataSource(source.value()); } } catch (Exception e) { System.out.println(clazz + ":" + e.getMessage()); } } }
最後在spring組態檔中設定攔截規則就可以了,比如攔截service層或者dao層的所有方法:
<bean id="dataSourceAspect" class="com.test.context.datasource.DataSourceAspect" /> <aop:config> <aop:aspect ref="dataSourceAspect"> <!-- 攔截所有service方法 --> <aop:pointcut id="dataSourcePointcut" expression="execution(* com.test.*.dao.*.*(..))"/> <aop:before pointcut-ref="dataSourcePointcut" method="intercept" /> </aop:aspect> </aop:config> </bean>
OK,這樣就可以直接在類或者方法上使用註解@DataSource來指定資料來源,不需要每次都手動設定了。
範例程式碼如下:
@Service // 預設DataServiceImpl下的所有方法均存取資料來源1 @DataSource("dataSource1") public class DataServiceImpl implements DataService { @Autowired private DataMapper dataMapper; @Override public List<Map<String, Object>> getList1() { // 不指定,則預設使用資料來源1 return dataMapper.getList1(); } @Override // 覆蓋類上指定的,使用資料來源2 @DataSource("dataSource2") public List<Map<String, Object>> getList2() { return dataMapper.getList2(); } @Override // 覆蓋類上指定的,使用資料來源3 @DataSource("dataSource3") public List<Map<String, Object>> getList3() { return dataMapper.getList3(); } }
提示:註解@DataSource既可以加在方法上,也可以加在介面或者介面的實現類上,優先順序別:方法>實現類>介面。也就是說如果介面、介面實現類以及方法上分別加了@DataSource註解來指定資料來源,則優先以方法上指定的為準。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45