1、实现程序运行间动态切换数据源
2、实现数据库存储数据源
3、选择
dynamic-datasource集成了很多ORM的框架,其中,使用比较多的是druid,但有一些东西开始收费了
druid也可以自行配置,配置多了点
目前版本只支持单一位置加载数据源(只能从配置文件或者自定义加载数据源),不能多位置加载数据源
目前版本未实现动态添加数据源(在切换数据源时,不存在的数据源在数据库查询,添加进数据源连接池)
springboot版本:2.3.1.RELEASE
dynamic-datasource版本:3.5.1
官方文档:https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611
实现源代码地址
<dependency>
<groupId>com.baomidougroupId>
<artifactId>dynamic-datasource-spring-boot-starterartifactId>
<version>3.5.1version>
dependency>
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
/**
*租户数据源
*/
CREATE TABLE `t_tenant_data_source`
(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '数据源id',
`tenant_id` int(11) unsigned NOT NULL COMMENT '租户id',
`tenant_name` varchar(100) DEFAULT NULL COMMENT '租户名',
`microservice_db` tinyint(3) DEFAULT '1' COMMENT '微服务业务db 1:基础库 2:平台库 3:财务库...',
`jdbc_type` tinyint(1) DEFAULT '1' COMMENT 'db类型 1:mysql ',
`jdbc_uri` varchar(255) NOT NULL COMMENT '连接URL',
`jdbc_username` varchar(255) NOT NULL COMMENT '连接用户',
`jdbc_password` varchar(255) NOT NULL COMMENT '连接密码',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '类型状态 -1删除 0禁用 1启用 ',
`deleted_tag` tinyint(4) DEFAULT '0' COMMENT '删除标识 0:未删除 1:已删除',
`create_by_id` int(11) NOT NULL DEFAULT '0' COMMENT '创建人id',
`create_time` datetime NOT NULL COMMENT '创建时间',
`create_by_name` varchar(40) DEFAULT NULL COMMENT '创建人名',
`update_by_id` int(11) DEFAULT NULL COMMENT '更新人id',
`update_time` datetime DEFAULT NULL COMMENT '更新人时间',
`update_by_name` varchar(40) DEFAULT NULL COMMENT '更新人名',
PRIMARY KEY (`id`),
KEY `idx_tenant_tenant_id` (`tenant_id`) COMMENT '租户id'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='租户数据源';
package org.lwd.microservice.boot.common.aop;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.lwd.microservice.boot.common.entity.dto.TenantDataSourceDTO;
import org.lwd.microservice.boot.common.service.TenantDataSourceService;
import org.lwd.microservice.boot.core.constant.DataSourceConstant;
import org.lwd.microservice.boot.core.entity.BaseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.List;
/**
* 加载数据库所有数据源
*
* @author weidong
* @version V1.0.0
* @since 2023/6/13
*/
@Component
public class DataSourceChangeProvider {
@Autowired
private DataSource dataSource;
//微服务模式下,替换成TenantDataSourceDubboService或者其他
@Autowired
private TenantDataSourceService tenantDataSourceService;
@Autowired
@SuppressWarnings("all")
private DefaultDataSourceCreator dataSourceCreator;
/**
* 增加数据源
*
* @param dto 租户数据源信息
*/
public void addDataSource(TenantDataSourceDTO dto) {
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
DataSourceProperty dataSourceProperty = new DataSourceProperty();
int tenantId = dto.getTenantId();
String dsName = DataSourceConstant.TENANT_SOURCE_HEADER + tenantId;
dataSourceProperty.setPoolName(dsName);
dataSourceProperty.setDriverClassName("com.mysql.jdbc.Driver");
dataSourceProperty.setUrl(dto.getJdbcUri() + "?useUnicode=true&allowPublicKeyRetrieval=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true&nullCatalogMeansCurrent=true");
dataSourceProperty.setUsername(dto.getJdbcUsername());
dataSourceProperty.setPassword(dto.getJdbcPassword());
DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
ds.addDataSource(dsName, dataSource);
}
/**
* 移除数据源
*
* @param tenantId 租户ID
*/
public void delDataSource(Integer tenantId) {
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
ds.removeDataSource(DataSourceConstant.TENANT_SOURCE_HEADER + tenantId);
}
/**
* 切换数据源
*
* @param dsName 租户ID
*/
public void changeDataSource(String dsName) {
//清空当前线程数据源
//如果当前线程是连续切换数据源 只会移除掉当前线程的数据源名称
DynamicDataSourceContextHolder.poll();
DynamicDataSourceContextHolder.push(dsName);
}
@PostConstruct
public void initDataSourceAll() {
BaseResult<List<TenantDataSourceDTO>> listBaseResult = tenantDataSourceService.getTenantDataSourceList();
if (listBaseResult.isSuccess()) {
List<TenantDataSourceDTO> tenantDataSourceDTOList = listBaseResult.getData();
if (CollectionUtils.isNotEmpty(tenantDataSourceDTOList)) {
//数据源操作,也可以加入不同的数据源类型
for (TenantDataSourceDTO dto : tenantDataSourceDTOList) {
this.addDataSource(dto);
}
}
}
}
}
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 if (uriArr[uriArr.length - 1].equals("detail2")) {
log.info("datasource2------");
DynamicDataSourceContextHolder.push("slave_1");
} else {
log.info("datasource dynamic 3------");
DynamicDataSourceContextHolder.push("tenant_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 if (uriArr[uriArr.length - 1].equals("detail2")) {
log.info("interceptor datasource2------");
DynamicDataSourceContextHolder.push("slave_1");
} else {
log.info("interceptor datasource dynamic 3------");
DynamicDataSourceContextHolder.push("tenant_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);
}
}
TestDataSource.http
### 详情
GET http://localhost:8021/tenantDataSource/detail?pk = 1
### 详情2-动态切换
GET http://localhost:8021/tenantDataSource/detail2?pk = 1
### 详情3-加载数据库数据源
GET http://localhost:8021/tenantDataSource/detail3?pk = 1
原创不易,如若本文能够帮助到您的同学
支持我:关注我+点赞+收藏⭐️
留言:探讨问题,看到立马回复
格言:己所不欲勿施于人 扬帆起航、游历人生、永不言弃!