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