Spring?AOP實現(xiàn)多數(shù)據(jù)源動態(tài)切換
需求背景
去年底,公司項目有一個需求中有個接口需要用到平臺、算法、大數(shù)據(jù)等三個不同數(shù)據(jù)庫的數(shù)據(jù)進(jìn)行計算、組裝以及最后的展示,當(dāng)時這個需求是另一個老同事在做,我只是負(fù)責(zé)自己的部分。
直到今年回來了,這個項目也做得差不多了,這會兒才有時間區(qū)仔細(xì)看同事的代碼,是怎么去實現(xiàn)多數(shù)據(jù)源動態(tài)切換的。
擴(kuò)展:當(dāng)業(yè)務(wù)也來越復(fù)雜,數(shù)據(jù)量越來越龐大時,就可能會對數(shù)據(jù)庫進(jìn)行分庫分表、讀寫分離等設(shè)計來減輕壓力、提高系統(tǒng)性能,那么多數(shù)據(jù)源動態(tài)切換勢必是必不可少!
經(jīng)過了一星期零零碎碎的下班時間,從了解原理、實現(xiàn)、優(yōu)化的過程,自己終于總算是弄出來了,接下來一起看看!
思考
- 如何讓Spring知道我們配置了多個數(shù)據(jù)源?
- 配置了多個數(shù)據(jù)源后,Spring是如何決定使用哪一個數(shù)據(jù)源?
- Spring是如何動態(tài)切換數(shù)據(jù)源?
分析及實現(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這是我的兩個數(shù)據(jù)庫:本地數(shù)據(jù)庫+個人服務(wù)器數(shù)據(jù)庫
服務(wù)器數(shù)據(jù)庫

本地數(shù)據(jù)庫

Spring如何獲取配置好的多個數(shù)據(jù)源信息?
Spring提供了三種方式進(jìn)行獲取
@Value注解獲?。▽嶓w類需配合@Component),最簡單,但當(dāng)配置信息較多時,寫起來比較繁瑣
@ConfigurationProperties注解獲取,需要定義前綴,可大批量獲取配置信息
@Environment注解從Spring環(huán)境中獲取,實現(xiàn)較為復(fù)雜,本人很少用
同事使用的方式是第一種方式,但是我個人覺得這樣侵入性較大,每增加一個數(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;
}
}如此之后,確實是可以讀取YML中的數(shù)據(jù)源信息,但是總覺得怪怪的。
果然!當(dāng)我實現(xiàn)了整個功能后,我發(fā)現(xiàn),如果我想要再加一個數(shù)據(jù)源,我還是得去求改DBProperties和DataSourceConfig這兩類的內(nèi)容,就很煩,我這個人比較懶,所以我就將這部分內(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;
}
}這樣子,我之后無論配置了多少個數(shù)據(jù)源信息,我都不需要再去修改配置代碼
Spring如何選擇使用數(shù)據(jù)源?
選擇一個數(shù)據(jù)源
通過繼承AbstractRoutingDataSource接口,重寫determineCurrentLookupKey方法,選擇具體的數(shù)據(jù)源
@Slf4j
public class MultiDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return MultiDataSourceHolder.getDatasource();
}
}利用ThreadLocal實現(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)備工作做好,下面開始將動態(tài)切換操作串聯(lián)起來
利用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();
}
}好了!功能已然實現(xiàn),打完收工!

。。。。
如果我工作中也這樣,估計要被測試打死!為了敷衍一下,來進(jìn)行一下測試
一套代碼直接打完:
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();
}測試結(jié)果:紅框數(shù)據(jù)來自于服務(wù)器數(shù)據(jù)庫,綠框數(shù)據(jù)來自于本地數(shù)據(jù)庫

遇到的問題同一個類中,A方法調(diào)用B方法用AopContext.currentProxy()報錯問題:在類上加@EnableAspectJAutoProxy(exposeProxy = true)————解決!配置多數(shù)據(jù)源時,注意將url修改成jdbc-url切面時,用JoinPoint獲取方法,判斷是否被注解修飾(雖然純屬多余)結(jié)果為false————有待考究!
結(jié)語
到此這篇關(guān)于Spring AOP實現(xiàn)多數(shù)據(jù)源動態(tài)切換的文章就介紹到這了,更多相關(guān)Spring AOP多數(shù)據(jù)源動態(tài)切換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot和Vue實現(xiàn)動態(tài)二維碼的示例代碼
二維碼在現(xiàn)代社交和營銷活動中被廣泛使用,本文主要介紹了SpringBoot和Vue實現(xiàn)動態(tài)二維碼的示例代碼,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-02-02
Spring指定bean在哪個應(yīng)用加載(示例詳解)
本文通過實例代碼介紹了Spring指定bean在哪個應(yīng)用加載,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-08-08
使用JDBC實現(xiàn)數(shù)據(jù)訪問對象層(DAO)代碼示例
這篇文章主要介紹了使用JDBC實現(xiàn)數(shù)據(jù)訪問對象層(DAO)代碼示例,具有一定參考價值,需要的朋友可以了解下。2017-10-10
如何使用@Value和@PropertySource注入外部資源
這篇文章主要介紹了如何使用@Value和@PropertySource注入外部資源的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
springboot配置多數(shù)據(jù)源的一款框架(dynamic-datasource-spring-boot-starter
dynamic-datasource-spring-boot-starter 是一個基于 springboot 的快速集成多數(shù)據(jù)源的啟動器,今天通過本文給大家分享這款框架配置springboot多數(shù)據(jù)源的方法,一起看看吧2021-09-09
詳解Java使用super和this來重載構(gòu)造方法
這篇文章主要介紹了詳解Java使用super和this來重載構(gòu)造方法的相關(guān)資料,這里提供實例來幫助大家理解這部分內(nèi)容,需要的朋友可以參考下2017-08-08
Java函數(shù)式編程之通過行為參數(shù)化傳遞代碼
行為參數(shù)化就是可以幫助你處理頻繁變更的需求的一種軟件開發(fā)模式,這篇文章將給大家詳細(xì)的介紹一下Java函數(shù)式編程之行為參數(shù)化傳遞代碼,感興趣的同學(xué)可以參考閱讀下2023-08-08
javascript與jsp發(fā)送請求到servlet的幾種方式實例
本文分別給出了javascript發(fā)送請求到servlet的5種方式實例與 jsp發(fā)送請求到servlet的6種方式實例2018-03-03

