SpringBoot多資料源druid
-
- 依賴引入
- 資料源和druid的配置
- druid的servlet和filter設定代碼
- 多語言切換注解
- 枚舉資料源
- AOP 代碼
- 建立dataSource的Bean對象
- 自定義DynamicDataSource
- 設定資料源注解的有效範圍
- 循環依賴問題
- 效果
- 源碼
-
- AbstractRoutingDataSource類
依賴引入
主要包含JDBC、mysql、druid(需要logj4d的依賴)、aop(多資料源選擇時需要切面)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
資料源和druid的配置
多資料源我配了一個local、remote。
druid的基礎配置可以通過配置檔案也可以代碼配置,建議配置檔案。但是關于servlet和filter可以考慮通過的代碼設定。
####################################druid連接配接池配置-開始##########################################
#連接配接池類型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.filters=stat
### 配置初始化大小、最小、最大
#資料庫連接配接池初始化大小
spring.datasource.initialSize=100
#最小空閑數
spring.datasource.minIdle=500
#最大活躍數
spring.datasource.maxActive=1000
#配置擷取連接配接等待逾時的時間
spring.datasource.maxWait=60000
#配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接配接,機關是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
#配置一個連接配接在池中最小生存的時間,機關是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
#測試連接配接
spring.datasource.validationQuery=select 'x'
#申請連接配接的時候檢測,建議配置為true,不影響性能,并且保證安全性
spring.datasource.testWhileIdle=true
#擷取連接配接時執行檢測,建議關閉,影響性能
spring.datasource.testOnBorrow=false
#歸還連接配接時執行檢測,建議關閉,影響性能
spring.datasource.testOnReturn=false
#是否開啟PSCache,PSCache對支援遊标的資料庫性能提升巨大,oracle建議開啟,mysql下建議關閉
spring.datasource.poolPreparedStatements=false
#開啟poolPreparedStatements後生效
spring.datasource.maxOpenPreparedStatements=20
#配置擴充插件,常用的插件有=>stat:監控統計 log4j2:日志 wall:防禦sql注入
pring.datasource.filters.commons-log.connection-logger-name=stat,wall,log4j2
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000
# Druid WebStatFilter配置,url統計
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions='*.gif,*.png,*.jpg,*.html,*.js,*.css,*.ico,/druid/*'
# Druid StatViewServlet配置,監控界面配置
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.reset-enable=true
spring.datasource.druid.stat-view-servlet.login-username=root
spring.datasource.druid.stat-view-servlet.login-password=123456
########################druid連接配接池配置-結束##########################################
#資料庫配置
spring.datasource.local.username=root
spring.datasource.local.password=123456
spring.datasource.local.url=jdbc:mysql://127.0.0.1/spring_boot_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.local.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.remote.username=root
spring.datasource.remote.password=123456
spring.datasource.remote.url=jdbc:mysql://127.0.0.1/spring_boot_test_remote?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.remote.driver-class-name=com.mysql.jdbc.Driver
druid的servlet和filter設定代碼
如果完全按照上面的配置,這步需要忽略。
這個代碼就是手動設定servlet和filter,登入資訊和過濾不需要統計的請求。
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public ServletRegistrationBean druidServletRegistrationBean(){
ServletRegistrationBean<Servlet> servletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*");
Map<String,String> initParams = new HashMap<>();
initParams.put("loginUsername","admin");
initParams.put("loginPassword","123456");
//背景允許誰可以通路
//initParams.put("allow", "localhost"):表示隻有本機可以通路
//initParams.put("allow", ""):為空或者為null時,表示允許所有通路
initParams.put("allow","");
//deny:Druid 背景拒絕誰通路
//initParams.put("msb", "192.168.1.20");表示禁止此ip通路
servletRegistrationBean.setInitParameters(initParams);
return servletRegistrationBean;
}
//配置 Druid 監控 之 web 監控的 filter
//WebStatFilter:用于配置Web和Druid資料源之間的管理關聯監控統計
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
//exclusions:設定哪些請求進行過濾排除掉,進而不進行統計
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
//"/*" 表示過濾所有請求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
多語言切換注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
/**
* 切換資料源名稱
*/
DataSourceType value() default DataSourceType.REMOTE;
}
枚舉資料源
有多資料就設定多少
public enum DataSourceType {
REMOTE,
LOCAL
}
AOP 代碼
切面被注解的需要指定資料源的方法,這裡的設定的資料源是單個線程有效。但是也會出現互相調用這個時候就是最後一個調用為線程資料源。
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.spring_boot.jdbc.mult.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
// 銷毀資料源 在執行方法之後
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}
建立dataSource的Bean對象
加載兩個(可以有多個)資料源建立bean,并且設定一個預設資料源,将所有的資料源,放在Map中,執行個體化自定義DynamicDataSource(類名字自定義無影響);
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.remote")
public DataSource remoteDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.local")
//@ConditionalOnProperty(prefix = "spring.datasource.local", name = "enabled", havingValue = "true")
public DataSource localDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dataSource")
@Primary
public DynamicDataSource dataSource(DataSource remoteDataSource, DataSource localDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.REMOTE.name(), remoteDataSource);
targetDataSources.put(DataSourceType.LOCAL.name(), localDataSource);
return new DynamicDataSource(remoteDataSource, targetDataSources);
}
}
自定義DynamicDataSource
這個類就是我們自定義的dataSource對象的類,主要繼承AbstractRoutingDataSource抽象類,定義一個構造方法取設定預設資料源、設定所有資料源、檢查資料源,重寫determineCurrentLookupKey()每次擷取連接配接對象是取的資料源的key。
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
// afterPropertiesSet()方法調用時用來将targetDataSources的屬性寫入resolvedDataSources中的
super.afterPropertiesSet();
}
/**
* 根據Key擷取資料源的資訊
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
設定資料源注解的有效範圍
每次觸發資料源切換,單個線程的資料源都保持統一。沒有考慮一個線程中多次調用資料源切換,這個會導緻一個業務請求在多個資料源中進行,如果是讀寫分離設計,會有一定的資料延遲影響。需要開發手動控制整體業務資料源的一緻性。
public class DynamicDataSourceContextHolder {
/**
* 使用ThreadLocal維護變量,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,
* 是以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 設定資料源變量
* @param dataSourceType
*/
public static void setDataSourceType(String dataSourceType){
System.out.printf("切換到{%s}資料源", dataSourceType);
CONTEXT_HOLDER.set(dataSourceType);
}
/**
* 擷取資料源變量
* @return
*/
public static String getDataSourceType(){
return CONTEXT_HOLDER.get();
}
/**
* 清空資料源變量
*/
public static void clearDataSourceType(){
CONTEXT_HOLDER.remove();
}
}
控制層代碼
@RestController
public class TestController {
@Autowired
JdbcTemplate jdbcTemplate;
@GetMapping("/local")
@DataSource(value = DataSourceType.LOCAL)
public List<Map<String, Object>> local(){
List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from mmp");
return maps;
}
@GetMapping("/remote")
@DataSource(value = DataSourceType.REMOTE)
public List<Map<String, Object>> remote(){
List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from mmp");
return maps;
}
}
循環依賴問題
如果出現下面的問題就再的啟動類上面的加入
效果
druid位址和使用者名和密碼
源碼
AbstractRoutingDataSource類
我們在可以看到系統在擷取的datasource對象是擷取通過determineCurrentLookupKey()擷取多個datasource存放的Map,這樣我們如果我們需要多資料源動态切換隻需要重寫determineCurrentLookupKey()并且動态擷取的key,就可以實作動态對象擷取。
如果的多資料druid跑起來有問題可以試一下SpringBoot項目使用druid
SpringBoot學習之路
代碼位址
https://gitee.com/zhang798/spring-demo.git
分支 springBoot_druid_doubleDataSource
git clone https://gitee.com/zhang798/spring-demo.git -b springBoot_druid_doubleDataSource