springboot動態數據源:通過請求參數路由不同數據庫


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

參考:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM