1、主要目錄結構

- DataSourceConfig.java
- DataSourceProvider.java
- DataSourceProviderImpl.java
- RoutingDataSource.java
- RoutingDataSourceContext.java
- WebConfig.java
- DynamicDataSourceInterceptor.java
- EditorApplication.java
- application.yml
- pom.xml
2、文件
1)xml依賴
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <skiptests>true</skiptests> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--注意log4j是druid強依賴的不能少,web是因為druid有web界面可以訪問,也不能少--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.23</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.3.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
2)application.yml配置
server:
port: 8888
servlet.context-path: /db
# 驅動配置信息
spring:
profiles:
active: dev
jpa:
hibernate:
ddl-auto: none
generate-ddl: false
show-sql: false
properties:
hibernate:
format_sql: false
database-platform: org.hibernate.dialect.MySQL5Dialect
datasource:
druid:
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${dbs.database.host}:${dbs.database.port}/${dbs.database.databasename}?characterEncoding=UTF-8
username: ${dbs.database.username}
password: ${dbs.database.password}
# 初始化大小,最小,最大
initialSize: 5
minIdle: 5
maxActive: 500
# 配置獲取連接等待超時的時間
maxWait: 60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一個連接在池中最小生存的時間,單位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 動態數據源配置
datasource:
dynamic:
3)application-dev.properties文件配置
profile=dev # 研發配置文件 dbs.database.host= dbs.database.port= dbs.database.username= dbs.database.password= dbs.database.databasename= #動態數據源配置 dbs.dynamic.database.host= dbs.dynamic.database.port= dbs.dynamic.database.username= dbs.dynamic.database.password= dbs.dynamic.database.databasename.suffix=
4)啟動類關閉數據源自動配置
//關閉數據源自動配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableJpaRepositories(basePackages = "com.demo.dynamicdatasource.repository.dao")
public class DynamicDataSourceApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicDataSourceApplication.class, args);
}
}
5)動態數據源攔截器,截取路徑中最后一個參數作為數據庫的名稱,路由到該數據庫
package com.demo.dynamicdatasource.interceptor;
import com.demo.dynamicdatasource.config.datasource.RoutingDataSourceContext;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DynamicDataSourceInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String requestURI = request.getRequestURI();
String[] split = requestURI.split("/");
String dbName = split[split.length - 1];
System.out.println("...【RequestURI】...requestURI: " + requestURI);
System.out.println("...【DynamicDataSourceInterceptor】...動態數據源接收到參數 dbName: " + dbName);
RoutingDataSourceContext.setDataSourceRoutingKey(dbName);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清理動態數據源key
RoutingDataSourceContext.clear();
}
}
6)攔截器配置注冊
package com.demo.dynamicdatasource.config;
import com.demo.dynamicdatasource.interceptor.DynamicDataSourceInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 攔截器配置注冊
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration dataSourceInterceptorRegistration = registry.addInterceptor(new DynamicDataSourceInterceptor());
dataSourceInterceptorRegistration.addPathPatterns("/**");
dataSourceInterceptorRegistration.excludePathPatterns("");
}
}
7)生產對應的數據源,其中if判斷如果加上,參數沒有匹配到數據庫,使用默認數據源
package com.demo.dynamicdatasource.config.datasource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
@Slf4j
@Component
public class DataSourceProviderImpl implements DataSourceProvider {
@Value("${dbs.dynamic.database.username}")
private String userName;
@Value("${dbs.dynamic.database.password}")
private String password;
@Value("${dbs.dynamic.database.databasename.suffix}")
private String databaseSuffix;
@Value("${dbs.dynamic.database.host}")
private String host;
@Value("${dbs.dynamic.database.port}")
private int port;
@Override
public DataSource createDataSourceByBotCode(String dbName) {
try {
// if ("awesome".equals(dbName) || "bottag".equals(dbName)) {
return DataSourceBuilder.create()
.url("jdbc:mysql://" + host + ":" + port + "/" + dbName /*+ databaseSuffix*/ + "?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowMultiQueries=true")
.driverClassName("com.mysql.cj.jdbc.Driver")
.username(userName)
.password(password)
.build();
// }
} catch (Exception e) {
log.error("DataSourceProviderImpl createDataSourceByBotCode error", e);
}
return null;
}
}
8)數據源配置(不用動)
package com.demo.dynamicdatasource.config.datasource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@Slf4j
public class DataSourceConfig {
/**
* Master data source.
*/
@Bean("masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DataSource masterDataSource() {
log.info("create master datasource...");
return DruidDataSourceBuilder.create().build();
}
/**
* 動態路由數據源
* @param masterDataSource
* @return
*/
@Bean
@Primary
public DataSource routingDataSource(@Autowired @Qualifier("masterDataSource") DataSource masterDataSource) {
log.info("create routing datasource...");
Map<Object, Object> map = new HashMap<>();
map.put("masterDataSource", masterDataSource);
RoutingDataSource routing = new RoutingDataSource();
routing.setTargetDataSources(map);
routing.setDefaultTargetDataSource(masterDataSource);
return routing;
}
}
9)根據dbName獲取數據源(不用動)
package com.demo.dynamicdatasource.config.datasource;
import javax.sql.DataSource;
public interface DataSourceProvider {
/**
* 根據dbName獲取對應的數據源
* @param dbName
* @return
*/
DataSource createDataSourceByBotCode(String dbName);
}
10)動態路由數據源(不用動)
package com.demo.dynamicdatasource.config.datasource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class RoutingDataSource extends AbstractRoutingDataSource {
/**
* 動態數據源信息
*/
private final ConcurrentMap<Object, Object> dynamicTargetDataSources = new ConcurrentHashMap<>();
/**
* 數據源創建提供者
*/
@Resource
private DataSourceProvider dataSourceProvider;
@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
synchronized (this) {
super.setTargetDataSources(targetDataSources);
this.dynamicTargetDataSources.putAll(targetDataSources);
}
}
@Override
protected Object determineCurrentLookupKey() {
String dataSourceKey = RoutingDataSourceContext.getDataSourceRoutingKey();
if (StringUtils.isBlank(dataSourceKey)) {
//返回空,即使用默認數據源
return null;
}
//檢查是否存在該dataSource,不存在則創建
this.checkAndInitDataSource(dataSourceKey);
return dataSourceKey;
}
/**
* 檢查是否存在數據源,不存在則進行初始化數據源操作
* @param botCode
*/
private void checkAndInitDataSource(String botCode) {
if (!dynamicTargetDataSources.containsKey(botCode)) {
synchronized (this) {
if (!dynamicTargetDataSources.containsKey(botCode)) {
DataSource dataSource = dataSourceProvider.createDataSourceByBotCode(botCode);
if (null != dataSource) {
addDataSource(botCode, dataSource);
}
}
}
}
}
/**
* 添加數據源到動態數據源中
* @param dataSourceKey
* @param dataSource
* @return
*/
private synchronized boolean addDataSource(String dataSourceKey, DataSource dataSource) {
dynamicTargetDataSources.put(dataSourceKey, dataSource);
// 將map賦值給父類的TargetDataSources
setTargetDataSources(dynamicTargetDataSources);
// 將TargetDataSources中的連接信息放入resolvedDataSources管理
super.afterPropertiesSet();
return true;
}
}
11)動態數據源上下文(不用動)
package com.demo.dynamicdatasource.config.datasource;
public class RoutingDataSourceContext {
private RoutingDataSourceContext() {}
/**
* 存儲在ThreadLocal中的動態數據源key
*/
private static final ThreadLocal<String> DATA_SOURCE_KEY_THREAD_LOCAL = new ThreadLocal<>();
/**
* 獲取動態數據源key
* @return
*/
public static String getDataSourceRoutingKey() {
return DATA_SOURCE_KEY_THREAD_LOCAL.get();
}
/**
* 設置動態數據源key
* @param key
*/
public static void setDataSourceRoutingKey(String key) {
DATA_SOURCE_KEY_THREAD_LOCAL.set(key);
}
/**
* 清理線程局部變量
*/
public static void clear() {
DATA_SOURCE_KEY_THREAD_LOCAL.remove();
}
}
git地址:https://github.com/yoyogrape/dynamic-data-source/tree/master/dynamic-data-source1
