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

参考:

posted @ 2020-08-31 10:43  songjn  阅读(2636)  评论(0编辑  收藏  举报