spring注解 @PropertySource配置數(shù)據(jù)源全流程
@PropertySource數(shù)據(jù)源配置
一般在配置數(shù)據(jù)源是都會(huì)使用xml的方式注入,key-value在properties中管理;spring4.X已有著比較完善的注解來替換xml的配置方式。
使用xml配置數(shù)據(jù)源
通常我們使用xml配置數(shù)據(jù)源,使用SpEL獲取properties中的配置。
applicationContext.xml 中配置 dataSource 及 PreferencesPlaceholderConfigurer,使用 PropertyPlaceholderConfigurer進(jìn)行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連接池的數(shù)據(jù)源, --> <bean id="dataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource"> ? ? <!-- 數(shù)據(jù)源的別名 --> ? ? <property name="alias" value="${proxool.alias}" />? ? ? <!-- 驅(qū)動(dòng) --> ? ? <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}" />? ? ? <!-- 最大鏈接數(shù)--> ? ? <property name="maximumConnectionCount" value="${proxool.maximumConnectionCount}" />? ? ? <!-- 最小鏈接數(shù) --> ? ? <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 #...
使用javaBean配置數(shù)據(jù)源
DataSourceConfiguration類是數(shù)據(jù)源的javaBean配置方式,@Configuratio注解當(dāng)前類,
spring啟動(dòng)時(shí)會(huì)掃描被@Configuratio注解的類,注入當(dāng)前類中配置的方法bean;
當(dāng)然別忘了啟用注解掃描:
<context:annotation-config/> ? <context:component-scan base-package="com.XXX.test.dateSource"></context:component-scan>
@value注解讀取配置
@value中可以直接使用SpEL,獲取properties配置,成員變量也不需要getter、setter,不過還是有一個(gè)前提,需要配置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掃面當(dāng)前類時(shí),注入每個(gè)有@Bean注解的方法的返回值Bean, name屬性默認(rèn)為返回值類類名首字母小寫,這里自己設(shè)置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; ? ? ?} ?}
這時(shí)dataSource已被注入,使用時(shí)可注解注入,如下:
? ? @Autowired ? ? private ProxoolDataSource dataSource;
@PropertySource注解讀取配置
@PropertySource注解當(dāng)前類,參數(shù)為對(duì)應(yīng)的配置文件路徑,這種方式加載配置文件,可不用在xml中配置PropertiesFactoryBean引入jdbc.properties,使用時(shí)方便得多,DataSourceConfiguration不再需要成員變量,取而代之的是需要注入一個(gè)Environment環(huán)境配置,使用env.getProperty(key)獲取數(shù)據(jù):
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; ? ? ?} ?}
這里主要是說明注解的用法,所以沒有具體體現(xiàn)數(shù)據(jù)源全部參數(shù)的配置。對(duì)于有強(qiáng)迫癥的來說若項(xiàng)目中所有bean都使用注解,幾乎不太希望僅dataSource用xml類配置,換成類的方式類配置強(qiáng)迫感就消失了!
注解的spring多數(shù)據(jù)源配置及使用
前一段時(shí)間研究了一下spring多數(shù)據(jù)源的配置和使用,為了后期從多個(gè)數(shù)據(jù)源拉取數(shù)據(jù)定時(shí)進(jìn)行數(shù)據(jù)分析和報(bào)表統(tǒng)計(jì)做準(zhǔn)備。由于之前做過的項(xiàng)目都是單數(shù)據(jù)源的,沒有遇到這種場景,所以也一直沒有去了解過如何配置多數(shù)據(jù)源。
后來發(fā)現(xiàn)其實(shí)基于spring來配置和使用多數(shù)據(jù)源還是比較簡單的,因?yàn)閟pring框架已經(jīng)預(yù)留了這樣的接口可以方便數(shù)據(jù)源的切換。
先看一下spring獲取數(shù)據(jù)源的源碼
可以看到AbstractRoutingDataSource獲取數(shù)據(jù)源之前會(huì)先調(diào)用determineCurrentLookupKey方法查找當(dāng)前的lookupKey,這個(gè)lookupKey就是數(shù)據(jù)源標(biāo)識(shí)。
因此通過重寫這個(gè)查找數(shù)據(jù)源標(biāo)識(shí)的方法就可以讓spring切換到指定的數(shù)據(jù)源了。
第一步:創(chuàng)建一個(gè)DynamicDataSource的類
繼承AbstractRoutingDataSource并重寫determineCurrentLookupKey方法,代碼如下:
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { // 從自定義的位置獲取數(shù)據(jù)源標(biāo)識(shí) return DynamicDataSourceHolder.getDataSource(); } }
第二步:創(chuàng)建DynamicDataSourceHolder
用于持有當(dāng)前線程中使用的數(shù)據(jù)源標(biāo)識(shí),代碼如下:
public class DynamicDataSourceHolder { /** * 注意:數(shù)據(jù)源標(biāo)識(shí)保存在線程變量中,避免多線程操作數(shù)據(jù)源時(shí)互相干擾 */ 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(); } }
第三步:配置多個(gè)數(shù)據(jù)源
和第一步里創(chuàng)建的DynamicDataSource的bean,簡化的配置如下:
<!--創(chuàng)建數(shù)據(jù)源1,連接數(shù)據(jù)庫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> <!--創(chuàng)建數(shù)據(jù)源2,連接數(shù)據(jù)庫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> <!--創(chuàng)建數(shù)據(jù)源3,連接數(shù)據(jù)庫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和與之對(duì)應(yīng)的數(shù)據(jù)源 --> <entry key="dataSource1" value-ref="dataSource1"></entry> <entry key="dataSource2" value-ref="dataSource2"></entry> <entry key="dataSource3 " value-ref="dataSource3"></entry> </map> </property> <!-- 這里可以指定默認(rèn)的數(shù)據(jù)源 --> <property name="defaultTargetDataSource" ref="dataSource1" /> </bean>
到這里已經(jīng)可以使用多數(shù)據(jù)源了,在操作數(shù)據(jù)庫之前只要DynamicDataSourceHolder.setDataSource("dataSource2")即可切換到數(shù)據(jù)源2并對(duì)數(shù)據(jù)庫db2進(jìn)行操作了。
示例代碼如下:
@Service public class DataServiceImpl implements DataService { @Autowired private DataMapper dataMapper; @Override public List<Map<String, Object>> getList1() { // 沒有指定,則默認(rèn)使用數(shù)據(jù)源1 return dataMapper.getList1(); } @Override public List<Map<String, Object>> getList2() { // 指定切換到數(shù)據(jù)源2 DynamicDataSourceHolder.setDataSource("dataSource2"); return dataMapper.getList2(); } @Override public List<Map<String, Object>> getList3() { // 指定切換到數(shù)據(jù)源3 DynamicDataSourceHolder.setDataSource("dataSource3"); return dataMapper.getList3(); } }
----------------------------華麗的分割線----------------------------
但是問題來了,如果每次切換數(shù)據(jù)源時(shí)都調(diào)用DynamicDataSourceHolder.setDataSource("xxx")就顯得十分繁瑣了,而且代碼量大了很容易會(huì)遺漏,后期維護(hù)起來也比較麻煩。能不能直接通過注解的方式指定需要訪問的數(shù)據(jù)源呢,比如在dao層使用@DataSource("xxx")就指定訪問數(shù)據(jù)源xxx?當(dāng)然可以!前提是,再加一點(diǎn)額外的配置^_^。
首先,我們得定義一個(gè)名為DataSource的注解,代碼如下:
@Target({ TYPE, METHOD }) @Retention(RUNTIME) public @interface DataSource { String value(); }
然后,定義AOP切面以便攔截所有帶有注解@DataSource的方法,取出注解的值作為數(shù)據(jù)源標(biāo)識(shí)放到DynamicDataSourceHolder的線程變量中:
public class DataSourceAspect { /** * 攔截目標(biāo)方法,獲取由@DataSource指定的數(shù)據(jù)源標(biāo)識(shí),設(shè)置到線程存儲(chǔ)中以便切換數(shù)據(jù)源 * * @param point * @throws Exception */ public void intercept(JoinPoint point) throws Exception { Class<?> target = point.getTarget().getClass(); MethodSignature signature = (MethodSignature) point.getSignature(); // 默認(rèn)使用目標(biāo)類型的注解,如果沒有則使用其實(shí)現(xiàn)接口的注解 for (Class<?> clazz : target.getInterfaces()) { resolveDataSource(clazz, signature.getMethod()); } resolveDataSource(target, signature.getMethod()); } /** * 提取目標(biāo)對(duì)象方法注解和類型注解中的數(shù)據(jù)源標(biāo)識(shí) * * @param clazz * @param method */ private void resolveDataSource(Class<?> clazz, Method method) { try { Class<?>[] types = method.getParameterTypes(); // 默認(rèn)使用類型注解 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配置文件中配置攔截規(guī)則就可以了,比如攔截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來指定數(shù)據(jù)源,不需要每次都手動(dòng)設(shè)置了。
示例代碼如下:
@Service // 默認(rèn)DataServiceImpl下的所有方法均訪問數(shù)據(jù)源1 @DataSource("dataSource1") public class DataServiceImpl implements DataService { @Autowired private DataMapper dataMapper; @Override public List<Map<String, Object>> getList1() { // 不指定,則默認(rèn)使用數(shù)據(jù)源1 return dataMapper.getList1(); } @Override // 覆蓋類上指定的,使用數(shù)據(jù)源2 @DataSource("dataSource2") public List<Map<String, Object>> getList2() { return dataMapper.getList2(); } @Override // 覆蓋類上指定的,使用數(shù)據(jù)源3 @DataSource("dataSource3") public List<Map<String, Object>> getList3() { return dataMapper.getList3(); } }
提示:注解@DataSource既可以加在方法上,也可以加在接口或者接口的實(shí)現(xiàn)類上,優(yōu)先級(jí)別:方法>實(shí)現(xiàn)類>接口。也就是說如果接口、接口實(shí)現(xiàn)類以及方法上分別加了@DataSource注解來指定數(shù)據(jù)源,則優(yōu)先以方法上指定的為準(zhǔn)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringBoot多數(shù)據(jù)源配置并通過注解實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源
- SpringBoot多數(shù)據(jù)源讀寫分離的自定義配置問題及解決方法
- 使用SpringBoot配置多數(shù)據(jù)源的經(jīng)驗(yàn)分享
- springboot配置多個(gè)數(shù)據(jù)源兩種方式實(shí)現(xiàn)
- Spring配置數(shù)據(jù)源的三種方式(小結(jié))
- 解決springboot項(xiàng)目不配置數(shù)據(jù)源啟動(dòng)報(bào)錯(cuò)問題
- springboot整合多數(shù)據(jù)源配置方式
- Spring配置數(shù)據(jù)源流程與作用詳解
相關(guān)文章
Spark學(xué)習(xí)筆記 (二)Spark2.3 HA集群的分布式安裝圖文詳解
這篇文章主要介紹了Spark2.3 HA集群的分布式安裝,結(jié)合圖文與實(shí)例形式詳細(xì)分析了Spark2.3 HA集群分布式安裝具體下載、安裝、配置、啟動(dòng)及執(zhí)行spark程序等相關(guān)操作技巧,需要的朋友可以參考下2020-02-02Java多線程之ReentrantReadWriteLock源碼解析
這篇文章主要介紹了Java多線程之ReentrantReadWriteLock源碼解析,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-05-05Java中super關(guān)鍵字介紹以及super()的使用
這幾天看到類在繼承時(shí)會(huì)用到this和super,這里就做了一點(diǎn)總結(jié),下面這篇文章主要給大家介紹了關(guān)于Java中super關(guān)鍵字介紹以及super()使用的相關(guān)資料,需要的朋友可以參考下2022-01-01Java數(shù)據(jù)結(jié)構(gòu)之有向圖設(shè)計(jì)與實(shí)現(xiàn)詳解
有向圖是具有方向性的圖,由一組頂點(diǎn)和一組有方向的邊組成,每條方向的邊都連著一對(duì)有序的頂點(diǎn)。本文為大家介紹的是有向圖的設(shè)計(jì)與實(shí)現(xiàn),需要的可以參考一下2022-11-11Java生成訂單號(hào)或唯一id的高并發(fā)方案(4種方法)
本文主要介紹了Java生成訂單號(hào)或唯一id的高并發(fā)方案,包括4種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01