zoukankan      html  css  js  c++  java
  • 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

    参考:

  • 相关阅读:
    Niagara技术文档汇总
    cPickle对python对象进行序列化,序列化到文件或内存
    html = data.decode('gbk').encode('utf-8')
    用200行Python代码“换脸”
    No module named cv2
    JAVA Calendar具体解释
    千万别用模板给的list.size()巨坑
    div仿checkbox表单样式美化及功能
    动态规划0—1背包问题
    拒绝乱码:做外贸系统,打印俄文快递单时,避免俄文乱码的方法
  • 原文地址:https://www.cnblogs.com/songjn/p/13558402.html
Copyright © 2011-2022 走看看