項目中需要使用南大信通數(shù)據(jù)庫GBase,因此配置多數(shù)據(jù)源用以查詢不同數(shù)據(jù)庫的數(shù)據(jù).先做一個小Demo測試數(shù)據(jù)源動態(tài)切換钠糊。主要使用切面修改配置文件實現(xiàn)數(shù)據(jù)源切換垫言,再切點后置通知中再切回主數(shù)據(jù)源。
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 阿里的數(shù)據(jù)庫連接池依賴 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.4</version>
</dependency>
<!--lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!--spring aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
配置文件
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
ds:
master:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
slave:
url: jdbc:mysql://localhost:3306/datasource?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
initialSize: 10
minIdle: 10
maxActive: 100
maxWait: 60000
timeBetweenEvictionRunsMillis: 2000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: false
maxPoolPreparedStatementPerConnectionSize: 0
filters: stat,log4j
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
useGlobalDataSourceStat: true
server:
port: 8081
servlet:
context-path: /
mybatis:
type-aliases-package: com.data.datasource.entity
mapper-locations: classpath:mybatis/**/**.xml
# ?????????
# configLocation: classpath:mybatis/mybatis-config.xml
自定義注解類
/**
* 多數(shù)據(jù)源注解
* (這個注解可以加在某一個 service類上或者方法上瞻讽,通過 value 屬性來指定類或者方法應(yīng)該使用哪一個數(shù)據(jù)源)
* @author
* @since 2022-08-10
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DataSource {
/**
* 數(shù)據(jù)源名稱
* (如果一個方法上加了 @DataSource 注解鸳吸,但是沒有指定數(shù)據(jù)源名稱,默認(rèn)使用 master 數(shù)據(jù)源)
* @return
*/
String value() default DataSourceType.DEFAULT_DS_NAME;
多數(shù)據(jù)源切面類
/**
* 注解@DataSource切面
* @author
* @since 2022-08-10
*/
@Component
@Aspect
@Order(10)
public class DataSourceAspect {
/**
* 定義切點
* @annotation(cn.lzy.study.datasource.annotation.DataSource)--表示方法上有注解 @DataSource 就將方法攔截下來
* @within(cn.lzy.study.datasource.annotation.DataSource)--表示如果類上面有注解 @DataSource 就將該類中的方法攔截下來
*/
@Pointcut("@annotation(com.data.datasource.annotation.DataSource) || @within(com.data.datasource.annotation.DataSource)")
public void pc() {
}
@Around("pc()")
public Object around(ProceedingJoinPoint pjp) {
// 獲取方法上的有效注解
DataSource dataSouce = getDataSouce(pjp);
if (dataSouce != null) {
// 獲取注解中的值(即:數(shù)據(jù)源名稱)
String dataSourceName = dataSouce.value();
// 將該數(shù)據(jù)源名稱設(shè)置到線程中
DynamicDataSourceContextHolder.setDataSourceType(dataSourceName);
}
try {
// 放行方法(執(zhí)行真實方法)
return pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
} finally {
// 清除數(shù)據(jù)源
DynamicDataSourceContextHolder.clearDataSourceType();
}
return null;
}
/**
* 獲取方法上的注解對象
* @param pjp
* @return
*/
private DataSource getDataSouce(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
// 查找方法上的注解
DataSource annotation = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (annotation != null) { // 方法上存在注解
return annotation;
}
// 方法上不存在注解速勇,則返回類上的注解(signature.getDeclaringType()--獲取方法所在的類型)
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}
全局多數(shù)據(jù)源切面類
/**
* 全局?jǐn)?shù)據(jù)源切面
* @author
* @since 2022-08-13
*/
@Component
@Aspect
// 設(shè)置優(yōu)先級(執(zhí)行順序)晌砾,數(shù)字越小,優(yōu)先級越高
@Order(9)
public class GloalDataSourceAspect {
@Autowired
private HttpSession httpSession;
// 攔截切點:所有service層的任意類任意方法
@Pointcut("execution(* com.data.datasource.service.*.*(..))")
public void pc() {
}
@Around("pc()")
public Object around(ProceedingJoinPoint pjp) {
DynamicDataSourceContextHolder.setDataSourceType((String) httpSession.getAttribute(DataSourceType.DS_SESSION_KEY));
try {
return pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
} finally {
DynamicDataSourceContextHolder.clearDataSourceType();
}
return null;
}
}
數(shù)據(jù)源對象
/**
* 數(shù)據(jù)源對象
* (從配置文件獲取數(shù)據(jù)源值)
* @author
* @since 2022-08-10
*/
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
/**
* 數(shù)據(jù)池類型
*/
private String type;
/**
* 數(shù)據(jù)庫驅(qū)動
*/
private String driverClassName;
/**
* 數(shù)據(jù)源
*/
private Map<String, Map<String, String>> ds;
/**
* 初始連接數(shù)
*/
private Integer initialSize;
/**
* 最小連接池數(shù)量
*/
private Integer minIdle;
/**
* 最大連接池數(shù)量
*/
private Integer maxActive;
/**
* 配置獲取連接等待超時的時間
*/
private Integer maxWait;
/**
* 給數(shù)據(jù)源初始化數(shù)據(jù)
* (在這個方法里烦磁,給數(shù)據(jù)源設(shè)置公共屬性)
* @param druidDataSource 數(shù)據(jù)源养匈,但是這個數(shù)據(jù)源對象只包含 url、username都伪、password 三個核心屬性
* @return
*/
public DataSource initDataSource(DruidDataSource druidDataSource) {
druidDataSource.setInitialSize(this.minIdle);
druidDataSource.setMinIdle(this.minIdle);
druidDataSource.setMaxActive(this.maxActive);
druidDataSource.setMaxWait(this.maxWait);
return druidDataSource;
}
// ====================== Getter & Setter ========================
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public Map<String, Map<String, String>> getDs() {
return ds;
}
public void setDs(Map<String, Map<String, String>> ds) {
this.ds = ds;
}
public Integer getInitialSize() {
return initialSize;
}
public void setInitialSize(Integer initialSize) {
this.initialSize = initialSize;
}
public Integer getMinIdle() {
return minIdle;
}
public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}
public Integer getMaxActive() {
return maxActive;
}
public void setMaxActive(Integer maxActive) {
this.maxActive = maxActive;
}
public Integer getMaxWait() {
return maxWait;
}
public void setMaxWait(Integer maxWait) {
this.maxWait = maxWait;
}
}
數(shù)據(jù)源類型類
public interface DataSourceType {
String DEFAULT_DS_NAME = "master";
String DS_SESSION_KEY = "ds_session_key";
}
動態(tài)數(shù)據(jù)源核心實現(xiàn)類
/**
* 【動態(tài)多數(shù)據(jù)源切換功能實現(xiàn)-核心點】
* 當(dāng) mapper 需要獲取數(shù)據(jù)源時呕乎,會去 AbstractRoutingDataSource 類中進(jìn)行處理
* @author
* @since 2022-08-11
*/
@Component
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 該方法用來返回數(shù)據(jù)源名稱
* (當(dāng)系統(tǒng)需要獲取數(shù)據(jù)源的時候,會自動調(diào)用該方法獲取數(shù)據(jù)源的名稱)
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
public DynamicDataSource(LoadDataSource loadDataSource) {
// 1.設(shè)置所有的數(shù)據(jù)源
Map<String, DataSource> allDataSourceMap = loadDataSource.loadAllDataSource();
super.setTargetDataSources(new HashMap<>(allDataSourceMap));
// 2.設(shè)置默認(rèn)數(shù)據(jù)源(作用:并不是所有的方法上都有 @DataSource 注解陨晶,對于那些沒有 @DataSource 注解的方法猬仁,設(shè)置使用默認(rèn)數(shù)據(jù)源)
super.setDefaultTargetDataSource(allDataSourceMap.get(com.data.datasource.datasource.DataSourceType.DEFAULT_DS_NAME));
super.afterPropertiesSet();
}
}
數(shù)據(jù)源存儲類
/**
* 這個類用來存儲當(dāng)前線程所使用的數(shù)據(jù)源名稱
* @author
* @since 2022-08-10
*/
public class DynamicDataSourceContextHolder {
private static ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 設(shè)置數(shù)據(jù)源
* @param dataSourceType 數(shù)據(jù)源名稱
*/
public static void setDataSourceType(String dataSourceType) {
CONTEXT_HOLDER.set(dataSourceType);
}
/**
* 獲取數(shù)據(jù)源
* @return 數(shù)據(jù)源名稱
*/
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 清空數(shù)據(jù)源(防止內(nèi)存溢出、泄露)
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
數(shù)據(jù)源加載類
/**
* 加載數(shù)據(jù)源
* @author
* @since 2022-08-11
*/
@Component
@EnableConfigurationProperties(DruidProperties.class) // 引入該配置屬性
public class LoadDataSource {
@Autowired
private DruidProperties druidProperties;
/**
* 加載所有數(shù)據(jù)源
* @return
*/
public Map<String, DataSource> loadAllDataSource() {
// 所有的數(shù)據(jù)源緩存
Map<String, DataSource> map = new HashMap<>();
// 從配置屬性中獲取數(shù)據(jù)源信息
Map<String, Map<String, String>> ds = druidProperties.getDs();
try {
for(String dataSourceName : ds.keySet()) {
// 根據(jù)配置屬性信息創(chuàng)建數(shù)據(jù)源(得到的數(shù)據(jù)源僅有三個核心屬性:url先誉、username湿刽、password)
DataSource baseDataSource = DruidDataSourceFactory.createDataSource(ds.get(dataSourceName));
// 對獲取到的數(shù)據(jù)源,進(jìn)行公共屬性賦值
DataSource dataSource = druidProperties.initDataSource((DruidDataSource) baseDataSource);
// 將數(shù)據(jù)源存入到緩存中
map.put(dataSourceName, dataSource);
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
兩個數(shù)據(jù)源
image.png
image.png
接口調(diào)用
image.png
注解織入
image.png
image.png
調(diào)用成功案例
image.png
image.png