springboot dynamic-datasource 实现动态切换数据源-多租户-配置文件切换-基于dynamic-datasource

文章目录

  • 1、目标
  • 2、源代码
  • 3、实现
    • pom.xml配置
    • application.yml配置
    • 手动切换
    • 动态切换-aop模式
    • 动态切换-拦截器模式
  • 4、总结
  • 外传

1、目标

1、实现动态切换数据源
2、实现配置多数据源
3、实现读写分离也可以用多数据源方式
4、选择
dynamic-datasource集成了很多ORM的框架,其中,使用比较多的是druid,但有一些东西开始收费了
druid也可以自行配置,配置多了点

目前版本只支持单一位置加载数据源(只能从配置文件或者自定义加载数据源),不能多位置加载数据源
目前版本未实现动态添加数据源(在切换数据源时,不存在的数据源在数据库查询,添加进数据源连接池)

2、源代码

springboot版本:2.3.1.RELEASE
dynamic-datasource版本:3.5.1
官方文档:https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611
实现源代码地址

3、实现

pom.xml配置

	<dependency>
        <groupId>com.baomidougroupId>
        <artifactId>dynamic-datasource-spring-boot-starterartifactId>
        <version>3.5.1version>
    dependency>

application.yml配置


spring:
  application:
    name: microservice-boot-common
  # Jackson 配置项
  jackson:
    #    serialization:
    #      write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳
    #      write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
    #      write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
    #      fail-on-empty-beans: false # 允许序列化无属性的 Bean
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        druid: # Druid 【监控】相关的全局配置
          web-stat-filter:
            enabled: true
          stat-view-servlet:
            enabled: true
            allow: # 设置白名单,不填则允许所有访问
            url-pattern: /druid/*
            login-username: test
            login-password: test
          filter:
            stat:
              enabled: true
              log-slow-sql: true # 慢 SQL 记录
              slow-sql-millis: 1000
              merge-sql: true
            wall:
              config:
                multi-statement-allow: true
          initial-size: 4 # 初始连接数
          min-idle: 4 # 最小连接池数量
          max-active: 50 # 最大连接池数量
          max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
          time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
          min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
          max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
          validation-query: SELECT 1 # 配置检测连接是否有效
          test-while-idle: true
          test-on-borrow: false
          test-on-return: false
          name: master
        master:
          driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
          url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&allowPublicKeyRetrieval=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true&nullCatalogMeansCurrent=true
          username: root
          password: 123456
        slave_1:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/test2?useUnicode=true&allowPublicKeyRetrieval=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true&nullCatalogMeansCurrent=true
          username: root
          password: 123456




# MyBatis Plus 的配置项
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
  global-config:
    db-config:
      #      id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
      id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库
      #      id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
      #      id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
      logic-delete-field: deleted_tag
      table-prefix: t_
    banner: false
  type-aliases-package: org.lwd.microservice.boot.common.dao
  mapper-locations: classpath*:/mapper/**/*.xml

手动切换

    @DS("slave_1")
     @Override
    public BaseResult<TenantDataSourceDTO> getTenantDataSourceByPk(String pk) {
        BaseResult<TenantDataSourceDTO> baseResult = BaseResult.success();
        TenantDataSource domain = this.getById(pk);
        baseResult.setData(TenantDataSourceConvertor.INSTANCE.toDTO(domain));
        return baseResult;
     }

动态切换-aop模式

package org.lwd.microservice.boot.common.aop;

import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
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.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

/**
 * 数据源拦截-aop模式
 *
 * @author weidong
 * @version V1.0.0
 * @since 2023/6/13
 */
@Order(1)
@Aspect
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Component
@Slf4j
public class DataSourceChangeAdvisor {
    /**
     * 按需设置需要切换的模式
     */
    @Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
    public void pointDataSource() {
    }

    /**
     * 需要定义模块的规则
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointDataSource()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            //获取当前请求对象
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                String uri = request.getRequestURI();
                //String url = request.getRequestURL().toString();
                String[] uriArr = uri.split("/");
                if (uriArr[uriArr.length - 1].equals("detail")) {
                    log.info("datasource1------");
                    DynamicDataSourceContextHolder.push("master");
                } else {
                    log.info("datasource2------");
                    DynamicDataSourceContextHolder.push("slave_1");
                } 
            }
        } catch (Exception e) {
            log.error("日志拦截异常", e);
        } finally {
            return joinPoint.proceed();
        }
    }
}


动态切换-拦截器模式

package org.lwd.microservice.boot.common.interceptor;

import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 动态数据源-拦截器模式-需要自定义规则
 * @author weidong
 * @version V1.0.0
 * @since 2023/6/16
 */
@Slf4j
public class DynamicDataSourceChangeInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();
        //String url = request.getRequestURL().toString();
        String[] uriArr = uri.split("/");
        if (uriArr[uriArr.length - 1].equals("detail")) {
            log.info("interceptor datasource1------");
            DynamicDataSourceContextHolder.push("master");
        } else {
            log.info("interceptor datasource2------");
            DynamicDataSourceContextHolder.push("slave_1");
        } 
        return true;
    }
}

package org.lwd.microservice.boot.common.interceptor;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * mvc拦截器-自定义拦截路径
 * @author weidong
 * @version V1.0.0
 * @since 2023/6/16
 */
@Configuration
public class DynamicDataSourceChangeInterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //如果拦截全部可以设置为 /**
        String [] path = {"/tenantDataSource/**"};
        //不需要拦截的接口路径
        String [] excludePath = {};
        DynamicDataSourceChangeInterceptor dynamicDataSourceChangeInterceptor = new DynamicDataSourceChangeInterceptor();
        registry.addInterceptor(dynamicDataSourceChangeInterceptor).addPathPatterns(path).excludePathPatterns(excludePath);
    }
}

4、总结

这种方式是配置文件的方式,可对立的配置文件配置多个数据源,
但是动态增加或者减少数据源,需要自己实现一部分代码,看下一篇文章

外传

 原创不易,如若本文能够帮助到您的同学
 支持我:关注我+点赞+收藏⭐️
 留言:探讨问题,看到立马回复
 格言:己所不欲勿施于人 扬帆起航、游历人生、永不言弃!

你可能感兴趣的:(分布式微服务,数据库,spring,boot,java,spring)