SpringBoot + Druid + Dynamic Datasource 多數(shù)據(jù)源配置方案
1、依賴
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>4.3.1</version>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.24<</version>
</dependency>2、application.yml
特別注意:多數(shù)據(jù)源 druid 要配置到每一個(gè)數(shù)據(jù)源里面
spring:
application:
name: DynamicDatasource
datasource:
druid:
stat-view-servlet:
enabled: true
allow:
deny:
reset-enable: false
login-username: admin
login-password: 123456
url-pattern: /druid/*
web-stat-filter:
enabled: true
#對(duì)這些請(qǐng)求放行
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
#攔截該項(xiàng)目下的一切請(qǐng)求
url-pattern: /*
session-stat-enable: true
principal-session-name: session_name
principal-cookie-name: cookie_name
profile-enable: true
# 包匹配,表示匹配以com.github.wxhnyfy.dynamicdatasource開頭的包名,多個(gè)用逗號(hào)隔開
aop-patterns: com.github.wxhnyfy.dynamicdatasource.*
dynamic:
primary: master # 設(shè)置默認(rèn)的數(shù)據(jù)源或者數(shù)據(jù)源組,默認(rèn)值即為 master
datasource:
master:
#設(shè)置默認(rèn)的數(shù)據(jù)源
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/region?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
# druid連接池設(shè)置
druid:
# 配置初始化線程數(shù)
initialSize: 5
# 最小線程數(shù)
minIdle: 5
# CPU核數(shù)+1,也可以大些但不要超過(guò)20,數(shù)據(jù)庫(kù)加鎖時(shí)連接過(guò)多性能下降
maxActive: 11
# 最大等待時(shí)間,內(nèi)網(wǎng):800,外網(wǎng):1200(三次握手1s)
maxWait: 60000
# 連接可空閑存活時(shí)間(ms)
timeBetweenEvictionRunsMillis: 60000
# 連接保持空閑而不被驅(qū)逐的最長(zhǎng)存活時(shí)間(ms)
minEvictableIdleTimeMillis: 300000
# 用來(lái)檢測(cè)連接是否有效的sql,如果validationQuery為空,那么testOnBorrow、testOnReturn、testWhileIdle這三個(gè)參數(shù)都不會(huì)起作用
validationQuery: SELECT 1
# 建議配置為true,不影響性能,并且保證安全性。申請(qǐng)連接的時(shí)候檢測(cè),如果空閑時(shí)間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測(cè)連接是否有效;
testWhileIdle: true
# 建議配置為false,申請(qǐng)連接時(shí)執(zhí)行validationQuery檢測(cè)連接是否有效,做了這個(gè)配置會(huì)降低性能。
testOnBorrow: false
# 建議配置為false,歸還連接時(shí)執(zhí)行validationQuery檢測(cè)連接是否有效,做了這個(gè)配置會(huì)降低性能;
testOnReturn: false
# PSCache對(duì)支持游標(biāo)的數(shù)據(jù)庫(kù)性能提升巨大,oracle建議開啟,mysql下建議關(guān)閉
poolPreparedStatements: false
# 保持minIdle數(shù)量的長(zhǎng)連接
keepAlive: true
# 要啟用PSCache,必須配置大于0,當(dāng)大于0時(shí),poolPreparedStatements自動(dòng)觸發(fā)修改為true。
# 在Druid中,不會(huì)存在Oracle下PSCache占用內(nèi)存過(guò)多的問(wèn)題,可以把這個(gè)數(shù)值配置大一些,比如說(shuō)100。缺省值為-1
# 開啟poolPreparedStatments后生效
maxPoolPreparedStatementPerConnectionSize: 20
# 是否合并多個(gè)DruidDataSource的監(jiān)控?cái)?shù)據(jù)
useGlobalDataSourceStat: true
# 配置監(jiān)控統(tǒng)計(jì)攔截的filters
filters: stat,wall,slf4j
stat:
logSlowSql: true
slowSqlMillis: 2000
mergeSql: true
dbType: mysql
wall:
# 可配置項(xiàng)參考類:com.alibaba.druid.wall.WallConfig
multi-statement-allow: true
alter-table-allow: false
drop-table-allow: false
slf4j:
# 只有當(dāng) isStatementExecutableSqlLogEnable() isStatementLogEnabled() 都為ture的情況才打印 可執(zhí)行sql
# 在{# LogFilter#logExecutableSql }使用
statement-executable-sql-log-enable: true
# 在{# LogFilter#logExecutableSql }使用
statement-log-enabled: true
statement-create-after-log-enabled: false
statement-log-error-enabled: true
result-set-log-enabled: false
#statementPrepareAfterLogEnable
# 準(zhǔn)備好的sql語(yǔ)句打?。ù藭r(shí)為執(zhí)行前)未進(jìn)行參數(shù)拼接
statement-prepare-after-log-enabled: false
#isStatementParameterSetLogEnabled
#打印參數(shù)
statement-parameter-set-log-enabled: false
#statementExecuteAfterLogEnable
#sql語(yǔ)句執(zhí)行完成后打?。▓?zhí)行后)未進(jìn)行參數(shù)拼接
statement-execute-after-log-enabled: false
#statementCloseAfterLogEnable
statement-close-after-log-enabled: false
#不打印清除參數(shù)日志
statement-parameter-clear-log-enable: false
# 通過(guò)connectProperties屬性來(lái)打開mergeSql功能;慢SQL記錄
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000
slave:
url: jdbc:mysql://localhost:3307/region?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# druid連接池設(shè)置
druid:
# 配置初始化線程數(shù)
initialSize: 5
# 最小線程數(shù)
minIdle: 5
# CPU核數(shù)+1,也可以大些但不要超過(guò)20,數(shù)據(jù)庫(kù)加鎖時(shí)連接過(guò)多性能下降
maxActive: 11
# 最大等待時(shí)間,內(nèi)網(wǎng):800,外網(wǎng):1200(三次握手1s)
maxWait: 60000
# 連接可空閑存活時(shí)間(ms)
timeBetweenEvictionRunsMillis: 60000
# 連接保持空閑而不被驅(qū)逐的最長(zhǎng)存活時(shí)間(ms)
minEvictableIdleTimeMillis: 300000
# 用來(lái)檢測(cè)連接是否有效的sql,如果validationQuery為空,那么testOnBorrow、testOnReturn、testWhileIdle這三個(gè)參數(shù)都不會(huì)起作用
validationQuery: SELECT 1
# 建議配置為true,不影響性能,并且保證安全性。申請(qǐng)連接的時(shí)候檢測(cè),如果空閑時(shí)間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測(cè)連接是否有效;
testWhileIdle: true
# 建議配置為false,申請(qǐng)連接時(shí)執(zhí)行validationQuery檢測(cè)連接是否有效,做了這個(gè)配置會(huì)降低性能。
testOnBorrow: false
# 建議配置為false,歸還連接時(shí)執(zhí)行validationQuery檢測(cè)連接是否有效,做了這個(gè)配置會(huì)降低性能;
testOnReturn: false
# PSCache對(duì)支持游標(biāo)的數(shù)據(jù)庫(kù)性能提升巨大,oracle建議開啟,mysql下建議關(guān)閉
poolPreparedStatements: false
# 保持minIdle數(shù)量的長(zhǎng)連接
keepAlive: true
# 要啟用PSCache,必須配置大于0,當(dāng)大于0時(shí),poolPreparedStatements自動(dòng)觸發(fā)修改為true。
# 在Druid中,不會(huì)存在Oracle下PSCache占用內(nèi)存過(guò)多的問(wèn)題,可以把這個(gè)數(shù)值配置大一些,比如說(shuō)100。缺省值為-1
# 開啟poolPreparedStatments后生效
maxPoolPreparedStatementPerConnectionSize: 20
# 是否合并多個(gè)DruidDataSource的監(jiān)控?cái)?shù)據(jù)
useGlobalDataSourceStat: true
# 配置監(jiān)控統(tǒng)計(jì)攔截的filters
filters: stat,wall,slf4j
stat:
logSlowSql: true
slowSqlMillis: 2000
mergeSql: true
dbType: mysql
wall:
# 可配置項(xiàng)參考類:com.alibaba.druid.wall.WallConfig
multi-statement-allow: true
alter-table-allow: false
drop-table-allow: false
slf4j:
# 只有當(dāng) isStatementExecutableSqlLogEnable() isStatementLogEnabled() 都為ture的情況才打印 可執(zhí)行sql
# 在{# LogFilter#logExecutableSql }使用
statement-executable-sql-log-enable: true
# 在{# LogFilter#logExecutableSql }使用
statement-log-enabled: true
statement-create-after-log-enabled: false
statement-log-error-enabled: true
result-set-log-enabled: false
#statementPrepareAfterLogEnable
# 準(zhǔn)備好的sql語(yǔ)句打?。ù藭r(shí)為執(zhí)行前)未進(jìn)行參數(shù)拼接
statement-prepare-after-log-enabled: false
#isStatementParameterSetLogEnabled
#打印參數(shù)
statement-parameter-set-log-enabled: false
#statementExecuteAfterLogEnable
#sql語(yǔ)句執(zhí)行完成后打?。▓?zhí)行后)未進(jìn)行參數(shù)拼接
statement-execute-after-log-enabled: false
#statementCloseAfterLogEnable
statement-close-after-log-enabled: false
#不打印清除參數(shù)日志
statement-parameter-clear-log-enable: false
# 通過(guò)connectProperties屬性來(lái)打開mergeSql功能;慢SQL記錄
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=20003、多數(shù)據(jù)源 Duid 自動(dòng)裝配詳解
3.1、自動(dòng)裝配 spring.datasource.dynamic
由于我們引入了 dynamic-datasource-spring-boot-starter,啟動(dòng)時(shí)會(huì)優(yōu)先自動(dòng)裝配 com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration,并且在類 com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure 之前裝配


會(huì)自動(dòng)裝配以下屬性,參考類 com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties,datasource 就是我們配置的多數(shù)據(jù)源


3.2、自動(dòng)裝配多數(shù)據(jù)源 datasource
參考類 com.baomidou.dynamic.datasource.creator.DataSourceProperty

3.3、自動(dòng)裝配每個(gè)數(shù)據(jù)源下的 duid 配置
這個(gè)配置和 Druid 的層級(jí)稍有一些不同
參考類 com.baomidou.dynamic.datasource.creator.druid.DruidConfig


數(shù)據(jù)源的 Filter 配置



3.4、自動(dòng)裝配 StatViewServlet
參考類:com.alibaba.druid.spring.boot.autoconfigure.stat.DruidStatViewServletConfiguration


3.5、自動(dòng)裝配 WebStatFilter
參考類:com.alibaba.druid.spring.boot.autoconfigure.stat.DruidWebStatFilterConfiguration


3.6、自動(dòng)裝配 AopPatterns
參考類:com.alibaba.druid.spring.boot.autoconfigure.stat.DruidSpringAopConfiguration


3.7、Filter 配置類
- wall: com.alibaba.druid.wall.WallConfig
- slf4j、log4j、log4j2、commonsLog: 四個(gè)日志 filter 只有實(shí)現(xiàn)不同,配置類都是同一個(gè),com.alibaba.druid.filter.logging.LogFilter
- stat:com.alibaba.druid.filter.stat.StatFilter
日志 Filter 要引入對(duì)應(yīng)的依賴,Druid 的 LoggerName 在類 com.alibaba.druid.filter.logging LogFilter,配置日志打印級(jí)別時(shí)需要使用下面的 LoggerName

4、Druid 監(jiān)控頁(yè)面
4.1、首頁(yè)
訪問(wèn)地址:http://IP:端口/上下文/druid/index.html

4.2、數(shù)據(jù)源列表
要看每一個(gè)數(shù)據(jù)源的 filter 類名,看一下是否和啟用的 Filter 一致,再看一起其他的配置是否能正常讀取

4.2、SQL 監(jiān)控
執(zhí)行過(guò) SQL 后,Druid 能對(duì) SQL 進(jìn)行監(jiān)控,但是不能區(qū)分是哪個(gè)數(shù)據(jù)源執(zhí)行的。由于我們開啟了SQL合并,所以看到的SQL都會(huì)帶有占位符

點(diǎn)擊去能看到每條SQL的執(zhí)行詳情。

4.3、SQL 防火墻
能對(duì)黑白名單進(jìn)行統(tǒng)計(jì),但是不能區(qū)分是哪個(gè)數(shù)據(jù)源執(zhí)行的

示例,我們?cè)?spring.datasource.dynamic.datasource.master.wall 配置了不能執(zhí)行 select 語(yǔ)句

SQL 會(huì)執(zhí)行報(bào)錯(cuò)

能正常監(jiān)控到黑名單 SQL

4.4、Web 應(yīng)用

4.5、URI 監(jiān)控
能監(jiān)控到執(zhí)行SQL的請(qǐng)求



4.6、Web Session 監(jiān)控


4.7、Spring 監(jiān)控
根據(jù) aop-pattern 配置包名進(jìn)行監(jiān)控


4.8、JSON API
把各個(gè)頁(yè)面的信息轉(zhuǎn)為 JSON 數(shù)據(jù)


5、動(dòng)態(tài)切換數(shù)據(jù)源
這里給出一個(gè)參考解決方案
5.1、數(shù)據(jù)源枚舉
public enum DataSourceEnum {
MASTER("master"),
SLAVE("slave");
private String value;
DataSourceEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}5.2、Controller 層
在 Controller 的每個(gè)方法入?yún)⒍技由蠑?shù)據(jù)源枚舉
@ApiOperation(value = "執(zhí)行自定義SQL")
@ApiImplicitParams({
@ApiImplicitParam(name = "envCode", value = "數(shù)據(jù)源標(biāo)識(shí)", required = true, paramType = "query", example = "MASTER", dataTypeClass = DataSourceEnum.class),
@ApiImplicitParam(name = "query", value = "查詢字符串", required = true, paramType = "query", example = "select * from chinapostoffice limit 100", dataTypeClass = String.class),
})
@GetMapping("/getChinaPostOffice ")
public Response<JSONArray> getChinaPostOffice(@RequestParam DataSourceEnum envCode,
@RequestParam String query
) {
logger.info(">>> query: {}", query);
List<Map<String, Object>> list = customMapper.selectList(query);
logger.info(">>> 總共 {} 條數(shù)據(jù)", list.size());
logger.info(">>> 數(shù)據(jù): {}", JSONArray.from(list, JSONWriter.Feature.WriteMapNullValue).toJSONString(JSONWriter.Feature.WriteMapNullValue));
return new Response<JSONArray>().success(JSONArray.from(list, JSONWriter.Feature.WriteMapNullValue));
}5.3、AOP 根據(jù)入?yún)⒆詣?dòng)切換數(shù)據(jù)源
DynamicDataSourceContextHolder 實(shí)現(xiàn)原理,可參考源碼,解析得很詳細(xì)了

@Aspect
@Component
// 確保在事務(wù)切面之前執(zhí)行
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AutoSwitchDataSourceAop {
private static final Logger logger = LoggerFactory.getLogger(AutoSwitchDataSourceAop.class);
/**
* 當(dāng)前指定的默認(rèn)數(shù)據(jù)源
*/
@Value("${spring.datasource.dynamic.primary}")
private String dynamicPrimaryDataSource;
/**
* 定義匹配需要切換數(shù)據(jù)源的方法的切點(diǎn)
* 匹配com.github.wxhnyfy.dynamicdatasource.controller包、com.github.wxhnyfy.dynamicdatasource.impl.service包下所有包含DataSourceEnum參數(shù)的方法
* 支持DataSourceEnum參數(shù)在任意參數(shù)位置(不限于第一個(gè)參數(shù))
*
* @param joinPoint 切點(diǎn)
* @return 目標(biāo)執(zhí)行方法
* @throws Throwable 異常
*/
@Around("execution(* com.github.wxhnyfy.dynamicdatasource.controller.*.*(com.github.wxhnyfy.dynamicdatasource.enums.DataSourceEnum, ..)) " +
"|| execution(* com.github.wxhnyfy.dynamicdatasource.impl.service.*.*(com.github.wxhnyfy.dynamicdatasource.enums.DataSourceEnum, ..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getMethod().getName();
logger.info("進(jìn)入 SwitchDataSourceAspect 的方法名: {}", methodName);
logger.debug("當(dāng)前指定的默認(rèn)數(shù)據(jù)源:{}", dynamicPrimaryDataSource);
DataSourceEnum envCode = null;
try {
// 獲取方法參數(shù)中的EnvCode值
envCode = extractEnvCode(joinPoint);
// 切換數(shù)據(jù)源
if (envCode != null) {
logger.info("數(shù)據(jù)庫(kù)環(huán)境標(biāo)識(shí):{}", envCode);
String previousDs = DynamicDataSourceContextHolder.peek();
logger.info("方法 [{}] 切換數(shù)據(jù)源: {} -> {}", methodName, previousDs, envCode.getValue());
DynamicDataSourceContextHolder.push(envCode.getValue());
}
// 執(zhí)行目標(biāo)方法
return joinPoint.proceed();
} finally {
// 清理數(shù)據(jù)源(恢復(fù)到默認(rèn)數(shù)據(jù)源)
if (envCode != null) {
DynamicDataSourceContextHolder.clear();
}
}
}
/**
* 通過(guò)反射獲取方法參數(shù)
* 自動(dòng)識(shí)別DataSourceEnum類型的參數(shù)
* 支持任意位置的DataSourceEnum參數(shù)
*
* @param joinPoint 切點(diǎn)
* @return DataSourceEnum參數(shù)
*/
private DataSourceEnum extractEnvCode(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Parameter[] parameters = signature.getMethod().getParameters();
// 遍歷參數(shù)查找EnvCode類型的參數(shù)
for (int i = 0; i < parameters.length; i++) {
if (parameters[i].getType().isAssignableFrom(DataSourceEnum.class)) {
Object arg = joinPoint.getArgs()[i];
if (arg instanceof DataSourceEnum) {
return (DataSourceEnum) arg;
}
}
}
return null;
}
}6、日志輸出效果
Mybatis-Plus 我們使用 org.apache.ibatis.logging.slf4j.Slf4jImpl 日志實(shí)現(xiàn),配置對(duì)應(yīng)的日志級(jí)別
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
logging:
level:
root: info
# 注意,logback不支持**匹配
com.github.wxhnyfy.**.mapper: debug
druid.sql.Statement: debug日志輸出效果如下圖

到此這篇關(guān)于SpringBoot + Druid + Dynamic Datasource 多數(shù)據(jù)源配置的文章就介紹到這了,更多相關(guān)SpringBoot Druid Dynamic Datasource 多數(shù)據(jù)源內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot mybatis plus druid多數(shù)據(jù)源解決方案 dynamic-datasource的使用詳解
- 一文詳解SpringBoot?Redis多數(shù)據(jù)源配置
- SpringBoot框架DataSource多數(shù)據(jù)源配置方式
- springboot Jpa多數(shù)據(jù)源(不同庫(kù))配置過(guò)程
- SpringBoot中配置多數(shù)據(jù)源的方法詳解
- springboot整合druid及多數(shù)據(jù)源配置的demo
- SpringBoot整合mysql、postgres及sqlserver實(shí)現(xiàn)多數(shù)據(jù)源配置實(shí)戰(zhàn)案例
相關(guān)文章
Request的包裝類HttpServletRequestWrapper的使用說(shuō)明
這篇文章主要介紹了Request的包裝類HttpServletRequestWrapper的使用說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Intellij無(wú)法創(chuàng)建java文件解決方案
這篇文章主要介紹了Intellij無(wú)法創(chuàng)建java文件解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
Java基于Base64實(shí)現(xiàn)編碼解碼圖片文件
這篇文章主要介紹了Java基于Base64實(shí)現(xiàn)編碼解碼圖片文件,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
mybatis-plus實(shí)體類中出現(xiàn)非數(shù)據(jù)庫(kù)映射字段解決辦法
這篇文章主要介紹了mybatis-plus實(shí)體類中出現(xiàn)非數(shù)據(jù)庫(kù)映射字段解決辦法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
Spring Security實(shí)現(xiàn)驗(yàn)證碼登錄功能
這篇文章主要介紹了Spring Security實(shí)現(xiàn)驗(yàn)證碼登錄功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01
java使用java.io.File類和java.nio.file.Path類對(duì)文件重命名
這篇文章主要給大家介紹了關(guān)于java使用java.io.File類和java.nio.file.Path類對(duì)文件重命名的相關(guān)資料,本文僅為日常操作記錄,方便后期使用查找本地電腦文件太多了,又不想一個(gè)一個(gè)重命名,改名字什么的很麻煩,需要的朋友可以參考下2024-02-02
Java數(shù)據(jù)結(jié)構(gòu)之線段樹詳解
線段樹是一種二叉搜索樹,與區(qū)間樹相似,它將一個(gè)區(qū)間劃分成一些單元區(qū)間,每個(gè)單元區(qū)間對(duì)應(yīng)線段樹中的一個(gè)葉結(jié)點(diǎn)。本文將介紹線段樹的Java實(shí)現(xiàn)代碼,需要的可以參考一下2022-01-01

