SpringBoot整合多数据源

一:pom引入

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--        <dependency>-->
        <!--            <groupId>org.springframework.security</groupId>-->
        <!--            <artifactId>spring-security-test</artifactId>-->
        <!--            <scope>test</scope>-->
        <!--        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <!--lombok的依赖,自动生成get/set方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>19.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>
        <!--mybatis-plus 代码生成依赖-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.3</version>
        </dependency>
        <!--mybatis-plus代码生成依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.tomcat</groupId>
                    <artifactId>tomcat-jdbc</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--aspectj依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

二:引入配置

1、DynamicDataSource类

package com.future.rabbit.config.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.Map;

/**
 * (切换数据源必须在调用service之前进行,也就是开启事务之前)
 * 动态数据源实现类
 */
public class DynamicDataSource extends AbstractRoutingDataSource {


    /**
     * 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
     * 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
     */
    @Override
    protected DataSource determineTargetDataSource() {
        return super.determineTargetDataSource();
    }

    /**
     * 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }

    /**
     * 设置默认数据源
     *
     * @param defaultDataSource
     */
    public void setDefaultDataSource(Object defaultDataSource) {
        super.setDefaultTargetDataSource(defaultDataSource);
    }

    /**
     * 设置数据源
     *
     * @param dataSources
     */
    public void setDataSources(Map<Object, Object> dataSources) {
        super.setTargetDataSources(dataSources);
        // 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
        DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
    }

}

2、DynamicDataSourceAspect

package com.future.rabbit.config.datasource;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.future.rabbit.config.exception.DatasourceException;
import com.future.rabbit.constant.RabbitConstant;
import com.future.rabbit.mapper.SysDatasourceMapper;
import com.future.rabbit.pojo.Datasource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.annotation.Annotation;
import java.util.Enumeration;

@Slf4j
@Aspect
@Component
@Order(1) // 请注意:这里order一定要小于tx:annotation-driven的order,即先执行DynamicDataSourceAspectAdvice切面,再执行事务切面,才能获取到最终的数据源
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DynamicDataSourceAspect {

    @Autowired
    SysDatasourceMapper datasourceMapper;

//    /**
//     * 普通Controller切面(interfaces)
//     */
//    @Pointcut("execution(public * com.future.rabbit.controller.*.*(..))&&!execution(public * com.future.rabbit.controller.DatasourceController.*(..))")
//    public void addInterfaces() {
//    }

    //todo 以后需要放行登录接口

    /**
     * 普通Controller切面(interfaces)
     */
    @Pointcut("execution(public * com.future.rabbit.controller.*.*(..))")
    public void addInterfaces() {
    }

    @Around("addInterfaces()")
    public Object Interceptor(ProceedingJoinPoint pjp) throws Throwable {
        Object result = null;
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Annotation anoIgnore = signature.getMethod().getAnnotation(IgnoreDynamicData.class);
        //如果是bpm操作业务数据 获取方法中的数据源参数作为拦截目标
        if (anoIgnore != null) {
            result = pjp.proceed();
        } else {
            RequestAttributes ra = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes sra = (ServletRequestAttributes) ra;
            HttpServletRequest request = sra.getRequest();
            // 如果有session则返回session如果没有则返回null(避免创建过多的session浪费内存)
            HttpSession session = request.getSession(false);
            // 获取请求头
            String tenantId = "";
            Enumeration<String> enumeration = request.getHeaderNames();
            while (enumeration.hasMoreElements()) {
                String name = enumeration.nextElement();
                if (name.equals(RabbitConstant.HEADER_TOKEN) || name.equals(RabbitConstant.HEADER_TOKEN_BACK)) {
                    String value = request.getHeader(name);
                    tenantId = value;
                    break;
                }
            }
            //获取用户所对应的数据源
            if (StringUtils.isNotBlank(tenantId)) {
                Boolean flag = false;
                if (tenantId.equals(RabbitConstant.DEFAULT_DATASOURCE)) {
                    //默认数据源放行
                    flag = true;
                } else {
                    //非默认数据源,需要判断是否合法
                    Datasource datasource = datasourceMapper.selectOne(new QueryWrapper<Datasource>().eq("tenant_id", tenantId));
                    if (datasource != null) {
                        flag = true;
                    }
                }
                if (flag) {
                    DynamicDataSourceContextHolder.setDataSourceKey(tenantId);
                    result = pjp.proceed();
                    DynamicDataSourceContextHolder.clearDataSourceKey();
                } else {
                    log.error("token验证异常,tenantId不合法,请重新登录获取token");
                    throw new DatasourceException(401, "token验证异常,tenantId不合法,请重新登录获取token");
                }
            } else {
                log.error("token验证异常,tenantId为空,请重新登录获取token");
                throw new DatasourceException(401, "token验证异常,tenantId不合法,请重新登录获取token");
            }
        }
        return result;
    }
}

3、DynamicDataSourceContextHolder

package com.future.rabbit.config.datasource;

import com.future.rabbit.constant.RabbitConstant;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * (切换数据源必须在调用service之前进行,也就是开启事务之前)
 * 动态数据源上下文
 */
public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
        /**
         * 将 master 数据源的 key作为默认数据源的 key
         */
        @Override
        protected String initialValue() {
            return RabbitConstant.DEFAULT_DATASOURCE;
        }
    };
    /**
     * 数据源的 key集合,用于切换时判断数据源是否存在
     */
    public static List<Object> dataSourceKeys = new ArrayList<>();

    /**
     * 切换数据源
     *
     * @param key
     */
    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }

    /**
     * 获取数据源
     *
     * @return
     */
    public static String getDataSourceKey() {
        return contextHolder.get();
    }

    /**
     * 重置数据源
     */
    public static void clearDataSourceKey() {
        contextHolder.remove();
    }

    /**
     * 判断是否包含数据源
     *
     * @param key 数据源key
     * @return
     */
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }

    /**
     * 添加数据源keys
     *
     * @param keys
     * @return
     */
    public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
        return dataSourceKeys.addAll(keys);
    }
}

4、DynamicDataSourceInit

package com.future.rabbit.config.datasource;

import com.future.rabbit.constant.RabbitConstant;
import com.future.rabbit.pojo.Datasource;
import com.future.rabbit.service.impl.SysDatasourceImpl;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.InlineShardingStrategyConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 初始化动态数据源
 */
@Slf4j
@Configuration
public class DynamicDataSourceInit {

    @Autowired
    private SysDatasourceImpl sysDatasource;

    @Resource
    private ApplicationContext applicationContext;

    @PostConstruct
    public void InitDataSource() {

        DynamicDataSource dynamicDataSource = (DynamicDataSource) applicationContext.getBean(RabbitConstant.DYNAMIC_DATASOURCE);
        HikariDataSource master = (HikariDataSource) applicationContext.getBean(RabbitConstant.DEFAULT_DATASOURCE);
        log.info("=====初始化动态数据源=====");
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(RabbitConstant.DEFAULT_DATASOURCE, master);
        List<Datasource> list = sysDatasource.list();
        //新增数据源
        if (list != null && list.size() > 0) {
            for (Datasource datasource : list) {
                HikariDataSource hikariDataSource = new HikariDataSource();
                BeanUtils.copyProperties(datasource, hikariDataSource);
                dataSourceMap.put(datasource.getTenantId(), hikariDataSource);
            }
        }
        //设置数据源
        dynamicDataSource.setDataSources(dataSourceMap);
        /**
         * 必须执行此操作,才会重新初始化AbstractRoutingDataSource 中的 resolvedDataSources,也只有这样,动态切换才会起效
         */
        dynamicDataSource.afterPropertiesSet();
    }

}

5、IgnoreDynamicData

package com.future.rabbit.config.datasource;//package com.future.rabbit.config.config1;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 需要被忽略动态数据源的方法
 * john
 */
@Target({ElementType.METHOD})
@Retention(RUNTIME)
@Documented
public @interface IgnoreDynamicData {
    String value() default "";
}

6、MybatisPlusConfig

package com.future.rabbit.config.datasource;

import com.baomidou.mybatisplus.core.parser.ISqlParser;
import com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.future.rabbit.constant.RabbitConstant;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: guomh
 * @Date: 2019/11/06
 * @Description: mybatis配置*
 */
@EnableTransactionManagement
@Configuration
@MapperScan({"com.future.rabbit.mapper"})
public class MybatisPlusConfig {

    @Bean(RabbitConstant.DEFAULT_DATASOURCE)
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    public DataSource master() {
        return DataSourceBuilder.create().build();
    }

    @Bean(RabbitConstant.DYNAMIC_DATASOURCE)
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(RabbitConstant.DEFAULT_DATASOURCE, master());
        // 将 master 数据源作为默认指定的数据源
        dynamicDataSource.setDefaultDataSource(master());
        dynamicDataSource.setDataSources(dataSourceMap);
        return dynamicDataSource;
    }

    @Bean
    public MybatisSqlSessionFactoryBean sqlSessionFactoryBean() throws Exception {
        MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
        /**
         * 重点,使分页插件生效
         */
        Interceptor[] plugins = new Interceptor[1];
        plugins[0] = paginationInterceptor();
        sessionFactory.setPlugins(plugins);
        //配置数据源,此处配置为关键配置,如果没有将 dynamicDataSource作为数据源则不能实现切换
        sessionFactory.setDataSource(dynamicDataSource());
        sessionFactory.setTypeAliasesPackage("com.future.rabbit.pojo");    // 扫描Model
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sessionFactory.setMapperLocations(resolver.getResources("classpath*:mapper/*/*Mapper.xml"));    // 扫描映射文件
        return sessionFactory;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        // 配置事务管理, 使用事务时在方法头部添加@Transactional注解即可
        return new DataSourceTransactionManager(dynamicDataSource());
    }

    /**
     * 加载分页插件
     *
     * @return
     */
    @Bean
    public Interceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        List<ISqlParser> sqlParserList = new ArrayList<>();
        // 攻击 SQL 阻断解析器、加入解析链
        sqlParserList.add(new BlockAttackSqlParser());
        paginationInterceptor.setSqlParserList(sqlParserList);
        return paginationInterceptor;
    }
}

7、DatasourceException

package com.future.rabbit.config.exception;

public class DatasourceException extends RuntimeException {


    private Integer code;

    private String message;

    public DatasourceException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}

三:启动类配置

注:引入exclude = DataSourceAutoConfiguration.class

package com.future.rabbit;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableAsync
public class RabbitApplication {

    public static void main(String[] args) {
        SpringApplication.run(RabbitApplication.class, args);
    }

}

四:yml配置

server:
  port: 8602
  tomcat:
    uri-encoding: UTF-8
    #最大工作线程数,默认200, 48g内存,线程数经验值800
    #操作系统做线程之间的切换调度是有系统开销的,所以不是越多越好。
    max-threads: 1000
    #等待队列长度,默认100
    accept-count: 1000
    max-connections: 20000
    #最小工作空闲线程数,默认10, 适当增大一些,以便应对突然增长的访问量
    min-spare-threads: 100
spring:
  application:
    name: rabbit-service
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin
    virtual-host: /
    #必须配置这个,生产者才会确认回调
    #publisher-confirm-type: correlated
    #publisher-returns: true
    # 手动提交消息
    listener:
      simple:
        acknowledge-mode: manual
      direct:
        acknowledge-mode: manual
  datasource:
    #username: root
    #password: root
    #url: jdbc:mysql://localhost:3306/rabbit_mq?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&autoReconnect=true&serverTimezone=UTC
    #driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      username: root
      password: root
      jdbc-url: jdbc:mysql://localhost:3306/rabbit_mq?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&autoReconnect=true&serverTimezone=UTC
      driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  mapper-locations: classpath:/mapper/**/*Mapper.xml
  typeAliasesPackage: com.future.rabbit.pojo
  configuration:
    map-underscore-to-camel-case: true

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值