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