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
