Spring?AOP實(shí)現(xiàn)多數(shù)據(jù)源動(dòng)態(tài)切換
需求背景
去年底,公司項(xiàng)目有一個(gè)需求中有個(gè)接口需要用到平臺(tái)、算法、大數(shù)據(jù)等三個(gè)不同數(shù)據(jù)庫(kù)的數(shù)據(jù)進(jìn)行計(jì)算、組裝以及最后的展示,當(dāng)時(shí)這個(gè)需求是另一個(gè)老同事在做,我只是負(fù)責(zé)自己的部分。
直到今年回來(lái)了,這個(gè)項(xiàng)目也做得差不多了,這會(huì)兒才有時(shí)間區(qū)仔細(xì)看同事的代碼,是怎么去實(shí)現(xiàn)多數(shù)據(jù)源動(dòng)態(tài)切換的。
擴(kuò)展:當(dāng)業(yè)務(wù)也來(lái)越復(fù)雜,數(shù)據(jù)量越來(lái)越龐大時(shí),就可能會(huì)對(duì)數(shù)據(jù)庫(kù)進(jìn)行分庫(kù)分表、讀寫分離等設(shè)計(jì)來(lái)減輕壓力、提高系統(tǒng)性能,那么多數(shù)據(jù)源動(dòng)態(tài)切換勢(shì)必是必不可少!
經(jīng)過了一星期零零碎碎的下班時(shí)間,從了解原理、實(shí)現(xiàn)、優(yōu)化的過程,自己終于總算是弄出來(lái)了,接下來(lái)一起看看!
思考
- 如何讓Spring知道我們配置了多個(gè)數(shù)據(jù)源?
- 配置了多個(gè)數(shù)據(jù)源后,Spring是如何決定使用哪一個(gè)數(shù)據(jù)源?
- Spring是如何動(dòng)態(tài)切換數(shù)據(jù)源?
分析及實(shí)現(xiàn)
配置多數(shù)據(jù)源信息
spring: datasource: local: database: local username: root password: jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver server: database: server username: root password: jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver
這是我的兩個(gè)數(shù)據(jù)庫(kù):本地?cái)?shù)據(jù)庫(kù)+個(gè)人服務(wù)器數(shù)據(jù)庫(kù)
服務(wù)器數(shù)據(jù)庫(kù)
本地?cái)?shù)據(jù)庫(kù)
Spring如何獲取配置好的多個(gè)數(shù)據(jù)源信息?
Spring提供了三種方式進(jìn)行獲取
@Value注解獲?。▽?shí)體類需配合@Component),最簡(jiǎn)單,但當(dāng)配置信息較多時(shí),寫起來(lái)比較繁瑣
@ConfigurationProperties注解獲取,需要定義前綴,可大批量獲取配置信息
@Environment注解從Spring環(huán)境中獲取,實(shí)現(xiàn)較為復(fù)雜,本人很少用
同事使用的方式是第一種方式,但是我個(gè)人覺得這樣侵入性較大,每增加一個(gè)數(shù)據(jù)源,就要重新定義變量然后用@Value去重新配置,很麻煩,所以我就選擇了第二種方式
通過@ConfigurationProperties注解獲取,需要定義前綴,可大批量獲取配置信息
@Data @Component @ConfigurationProperties(prefix = "spring.datasource") public class DBProperties { private HikariDataSource server; private HikariDataSource local; }
將所有的數(shù)據(jù)源加載到Spring中,可供其選擇使用
@Slf4j @Configuration public class DataSourceConfig { @Autowired private DBProperties dbProperties; @Bean(name = "multiDataSource") public MultiDataSource multiDataSource(){ MultiDataSource multiDataSource = new MultiDataSource(); //1.設(shè)置默認(rèn)數(shù)據(jù)源 multiDataSource .setDefaultTargetDataSource(dbProperties.getLocal()); //2.配置多數(shù)據(jù)源 HashMap<Object, Object> dataSourceMap = Maps.newHashMap(); dataSourceMap.put("local", dbProperties.getLocal()); dataSourceMap.put("server", dbProperties.getServer()); //3.存放數(shù)據(jù)源集 multiDataSource.setTargetDataSources(dataSourceMap); return multiDataSource; } }
如此之后,確實(shí)是可以讀取YML中的數(shù)據(jù)源信息,但是總覺得怪怪的。
果然!當(dāng)我實(shí)現(xiàn)了整個(gè)功能后,我發(fā)現(xiàn),如果我想要再加一個(gè)數(shù)據(jù)源,我還是得去求改DBProperties和DataSourceConfig這兩類的內(nèi)容,就很煩,我這個(gè)人比較懶,所以我就將這部分內(nèi)容優(yōu)化了一下:
優(yōu)化后的YML
spring: datasource: names: - database: dataSource0 username: root password: jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver - database: dataSource1 username: root password: jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver
優(yōu)化后的DBProperties
@Data @Component @ConfigurationProperties(prefix = "spring.datasource") public class DBProperties { private List<HikariDataSource> DBNames; }
優(yōu)化后的DataSourceConfig
@Slf4j @Configuration public class DataSourceConfig { @Autowired private DBProperties dbProperties; @Bean(name = "multiDataSource") public MultiDataSource multiDataSource(){ MultiDataSource multiDataSource = new MultiDataSource(); List<HikariDataSource> names = dbProperties.getNames(); if (CollectionUtils.isEmpty(names)){ throw new RuntimeException(" please configure the data source! "); } multiDataSource.setDefaultTargetDataSource(names.get(0)); HashMap<Object, Object> dataSourceMap = Maps.newHashMap(); int i = 0; for (HikariDataSource name : names) { dataSourceMap.put("dataSource"+(i++),name); } multiDataSource.setTargetDataSources(dataSourceMap); return multiDataSource; } }
這樣子,我之后無(wú)論配置了多少個(gè)數(shù)據(jù)源信息,我都不需要再去修改配置代碼
Spring如何選擇使用數(shù)據(jù)源?
選擇一個(gè)數(shù)據(jù)源
通過繼承AbstractRoutingDataSource接口,重寫determineCurrentLookupKey方法,選擇具體的數(shù)據(jù)源
@Slf4j public class MultiDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return MultiDataSourceHolder.getDatasource(); } }
利用ThreadLocal實(shí)現(xiàn)數(shù)據(jù)源線程隔離
public class MultiDataSourceHolder { private static final ThreadLocal<String> threadLocal =new ThreadLocal<>(); public static void setDatasource(String datasource){ threadLocal.set(datasource); } public static String getDatasource(){ return threadLocal.get(); } public static void clearDataSource(){ threadLocal.remove(); } }
準(zhǔn)備工作做好,下面開始將動(dòng)態(tài)切換操作串聯(lián)起來(lái)
利用AOP切面+自定義注解
自定義注解
@Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MultiDataSource { String DBName(); }
AOP切面
@Slf4j @Aspect @Component public class DataSourceAspect { @Pointcut(value = "@within(com.xiaozhao.base.aop.annotation.MultiDataSource) || @annotation(com.xiaozhao.base.aop.annotation.MultiDataSource)") public void dataSourcePointCut(){} @Before("dataSourcePointCut() && @annotation(multiDataSource)") public void before(MultiDataSource multiDataSource){ String dbName = multiDataSource.DBName(); if (StringUtils.hasLength(dbName)){ MultiDataSourceHolder.setDatasource(multiDataSource.DBName()); log.info("current dataSourceName ====== "+dbName); }else { log.info("switch datasource fail, use default, or please configure the data source for the annotations,"); } } @After("dataSourcePointCut()") public void after(){ MultiDataSourceHolder.clearDataSource(); } }
好了!功能已然實(shí)現(xiàn),打完收工!
。。。。
如果我工作中也這樣,估計(jì)要被測(cè)試打死!為了敷衍一下,來(lái)進(jìn)行一下測(cè)試
一套代碼直接打完:
Controller+Service+Dao
@RestController @RequestMapping("user") public class UserController { @Autowired private UserService userService; @GetMapping("/info") public UserVO getUser(){ return userService.creatUser(); } } public interface UserService { UserVO creatUser(); UserVO setUserInfo(String phone); } @Service @EnableAspectJAutoProxy(exposeProxy = true) public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Autowired private InfoMapper infoMapper; @Override public UserVO creatUser() { UserVO userVO = userMapper.getUserInfoMapper(); return ((UserService) AopContext.currentProxy()).setUserInfo(userVO.getPhone()); } @MultiDataSource(DBName = "dataSource1") public UserVO setUserInfo(String phone) { UserVO userInfo = infoMapper.getUserInfo(); UserVO user = new UserVO(); user.setUserName(userInfo.getUserName()); user.setPassword(userInfo.getPassword()); user.setAddress(userInfo.getAddress()); user.setPhone(phone); return user; } } @Mapper public interface InfoMapper { @Select("select id,user_name as userName,password,phone,address from test_user") UserVO getUserInfo(); } @Mapper public interface UserMapper { @Select("select id,user_name as userName,password,phone from user") UserVO getUserInfoMapper(); }
測(cè)試結(jié)果:紅框數(shù)據(jù)來(lái)自于服務(wù)器數(shù)據(jù)庫(kù),綠框數(shù)據(jù)來(lái)自于本地?cái)?shù)據(jù)庫(kù)
遇到的問題同一個(gè)類中,A方法調(diào)用B方法用AopContext.currentProxy()
報(bào)錯(cuò)問題:在類上加@EnableAspectJAutoProxy(exposeProxy = true)
————解決!配置多數(shù)據(jù)源時(shí),注意將url修改成jdbc-url切面時(shí),用JoinPoint獲取方法,判斷是否被注解修飾(雖然純屬多余)結(jié)果為false————有待考究!
結(jié)語(yǔ)
到此這篇關(guān)于Spring AOP實(shí)現(xiàn)多數(shù)據(jù)源動(dòng)態(tài)切換的文章就介紹到這了,更多相關(guān)Spring AOP多數(shù)據(jù)源動(dòng)態(tài)切換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot和Vue實(shí)現(xiàn)動(dòng)態(tài)二維碼的示例代碼
二維碼在現(xiàn)代社交和營(yíng)銷活動(dòng)中被廣泛使用,本文主要介紹了SpringBoot和Vue實(shí)現(xiàn)動(dòng)態(tài)二維碼的示例代碼,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-02-02Spring指定bean在哪個(gè)應(yīng)用加載(示例詳解)
本文通過實(shí)例代碼介紹了Spring指定bean在哪個(gè)應(yīng)用加載,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-08-08使用JDBC實(shí)現(xiàn)數(shù)據(jù)訪問對(duì)象層(DAO)代碼示例
這篇文章主要介紹了使用JDBC實(shí)現(xiàn)數(shù)據(jù)訪問對(duì)象層(DAO)代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-10-10如何使用@Value和@PropertySource注入外部資源
這篇文章主要介紹了如何使用@Value和@PropertySource注入外部資源的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06ANSI,Unicode,BMP,UTF等編碼概念實(shí)例講解
這篇文章主要介紹了ANSI,Unicode,BMP,UTF等編碼概念實(shí)例講解,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12springboot配置多數(shù)據(jù)源的一款框架(dynamic-datasource-spring-boot-starter
dynamic-datasource-spring-boot-starter 是一個(gè)基于 springboot 的快速集成多數(shù)據(jù)源的啟動(dòng)器,今天通過本文給大家分享這款框架配置springboot多數(shù)據(jù)源的方法,一起看看吧2021-09-09詳解Java使用super和this來(lái)重載構(gòu)造方法
這篇文章主要介紹了詳解Java使用super和this來(lái)重載構(gòu)造方法的相關(guān)資料,這里提供實(shí)例來(lái)幫助大家理解這部分內(nèi)容,需要的朋友可以參考下2017-08-08Java函數(shù)式編程之通過行為參數(shù)化傳遞代碼
行為參數(shù)化就是可以幫助你處理頻繁變更的需求的一種軟件開發(fā)模式,這篇文章將給大家詳細(xì)的介紹一下Java函數(shù)式編程之行為參數(shù)化傳遞代碼,感興趣的同學(xué)可以參考閱讀下2023-08-08javascript與jsp發(fā)送請(qǐng)求到servlet的幾種方式實(shí)例
本文分別給出了javascript發(fā)送請(qǐng)求到servlet的5種方式實(shí)例與 jsp發(fā)送請(qǐng)求到servlet的6種方式實(shí)例2018-03-03