场景
租户多且不固定且多服务场景动态实现
pom配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.7.RELEASE</version>
</parent>
<groupId>com.carsonlius</groupId>
<artifactId>dynamic-datasource-project</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>pd-goods</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<!-- 动态切换数据源头-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.0</version>
</dependency>
<!-- 接口文档-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- 配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<!-- 服务发现 可以搭配openfeign使用-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
</dependencies>
</project>
创建2个演示的数据库
CREATE TABLE `pd_auth_user` (
`id` bigint(20) NOT NULL COMMENT 'ID',
`account` varchar(30) NOT NULL COMMENT '账号',
`name` varchar(50) NOT NULL COMMENT '姓名',
`org_id` bigint(20) DEFAULT NULL COMMENT '组织ID\n#c_core_org',
`station_id` bigint(20) DEFAULT NULL COMMENT '岗位ID\n#c_core_station',
`email` varchar(255) DEFAULT NULL COMMENT '邮箱',
`mobile` varchar(20) DEFAULT '' COMMENT '手机',
`sex` varchar(1) DEFAULT 'N' COMMENT '性别\n#Sex{W:女;M:男;N:未知}',
`status` bit(1) DEFAULT b'0' COMMENT '启用状态 1启用 0禁用',
`avatar` varchar(255) DEFAULT '' COMMENT '头像',
`work_describe` varchar(255) DEFAULT '' COMMENT '工作描述\r\n比如: 市长、管理员、局长等等 用于登陆展示',
`password_error_last_time` datetime DEFAULT NULL COMMENT '最后一次输错密码时间',
`password_error_num` int(11) DEFAULT '0' COMMENT '密码错误次数',
`password_expire_time` datetime DEFAULT NULL COMMENT '密码过期时间',
`password` varchar(64) NOT NULL DEFAULT '' COMMENT '密码',
`last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
`create_user` bigint(20) DEFAULT '0' COMMENT '创建人id',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_user` bigint(20) DEFAULT '0' COMMENT '更新人id',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `UN_ACCOUNT` (`account`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='用户'
CREATE TABLE `pd_goods_info` (
`id` bigint(20) NOT NULL COMMENT '商品ID',
`code` char(16) COLLATE utf8mb4_bin NOT NULL COMMENT '商品编码',
`name` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '商品名称',
`bar_code` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '国条码',
`brand_id` bigint(20) DEFAULT NULL COMMENT '品牌表ID',
`one_category_id` bigint(20) DEFAULT NULL COMMENT '一级分类ID',
`two_category_id` bigint(20) DEFAULT NULL COMMENT '二级分类ID',
`three_category_id` bigint(20) DEFAULT NULL COMMENT '三级分类ID',
`supplier_id` bigint(20) DEFAULT NULL COMMENT '商品的供应商ID',
`price` decimal(8,2) NOT NULL COMMENT '商品售价价格',
`average_cost` decimal(18,2) NOT NULL COMMENT '商品加权平均成本',
`publish_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '上下架状态:0下架,1上架',
`audit_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '审核状态: 0未审核,1已审核',
`weight` float DEFAULT NULL COMMENT '商品重量',
`length` float DEFAULT NULL COMMENT '商品长度',
`height` float DEFAULT NULL COMMENT '商品重量',
`width` float DEFAULT NULL COMMENT '商品宽度',
`color` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '颜色',
`production_date` datetime NOT NULL COMMENT '生产日期',
`shelf_life` int(11) NOT NULL COMMENT '商品有效期',
`descript` text COLLATE utf8mb4_bin COMMENT '商品描述',
`update_time` datetime DEFAULT NULL,
`update_user` bigint(20) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`create_user` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='商品信息表'
3. 创建2个简单的接口
3.1 用户列表接口
package com.carsonlius.controller;
import com.carsonlius.dto.BaseDto;
import com.carsonlius.entity.User;
import com.carsonlius.services.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/user")
@Api(value = "用户模块", tags = "用户模块")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
@ApiOperation(value = "用户列表", response = List.class, httpMethod = "GET")
@ApiImplicitParams({
@ApiImplicitParam(name = "merchantId", value = "", dataType = "String", paramType = "query", required= true)
})
public List<User> lists(BaseDto baseDto){
return userService.lists();
}
}
3.2 商品列表接口
package com.carsonlius.controller;
import com.carsonlius.dto.BaseDto;
import com.carsonlius.entity.GoodsInfo;
import com.carsonlius.services.GoodsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/goods")
@Api(value = "商品模块", tags = "商品模块")
public class GoodsController {
@Autowired
private GoodsService goodsService;
@GetMapping("/list")
@ApiImplicitParams({
@ApiImplicitParam(name = "merchantId", value = "", dataType = "String", paramType = "query", required= true)
})
@ApiOperation(value = "获取全量商品列表", httpMethod = "GET", response = List.class)
public List<GoodsInfo> goods(BaseDto baseDto){
return goodsService.getGoods();
}
}
4. 定义基本上数据类型BaseDto (用来标识卖家信息 生产环境可以使用token替代)
package com.carsonlius.dto;
import lombok.Data;
@Data
public class BaseDto {
public String merchantId;
}
创建切面
package com.carsonlius.aspects;
import com.alibaba.druid.util.StringUtils;
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 com.carsonlius.config.PinDaConfig;
import com.carsonlius.dto.BaseDto;
import com.carsonlius.dto.DataSourceDTO;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.UUID;
@Component
@Aspect
public class DynamicChangeDatasource {
private Logger logger = LoggerFactory.getLogger(DynamicChangeDatasource.class);
@Autowired
private PinDaConfig pinDaConfig;
@Autowired
private DataSource dataSource;
@Autowired
private DefaultDataSourceCreator dataSourceCreator;
@Pointcut("execution(* com.carsonlius.controller.*.*(..))")
public void changeDatasource() {
}
@Around("changeDatasource()")
public Object around(ProceedingJoinPoint joinPoint) {
Object result = null;
String poolName = "";
try {
logger.info("开始加载数据库");
poolName = setDataSource(joinPoint);
result = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
} finally {
logger.info("执行切换数据源之后");
removeDatasource(poolName);
}
return result;
}
private void removeDatasource(String poolName) {
if (StringUtils.isEmpty(poolName)) {
logger.error("没有加载到数据库:" + poolName);
return;
}
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
ds.removeDataSource(poolName);
}
private String setDataSource(ProceedingJoinPoint joinPoint) {
BaseDto baseDto = null;
for (Object arg : joinPoint.getArgs()) {
System.out.println("arg" + arg);
if (arg instanceof BaseDto) {
baseDto = (BaseDto) arg;
break;
}
}
String merchantId = "goods";
if (baseDto != null) {
merchantId = baseDto.getMerchantId();
}
DataSourceDTO dto = new DataSourceDTO();
dto.setPassword(pinDaConfig.getPassword());
dto.setUsername(pinDaConfig.getUsername());
String poolName = wrapperPoolName(merchantId);
dto.setPoolName(poolName);
String url = "";
String ip = pinDaConfig.getIp();
String port = pinDaConfig.getPort();
if ("goods".equals(merchantId)) {
url = "jdbc:mysql://" + ip + ":" + port + "/pd_goods?serverTimezone=CTT&characterEncoding=utf8&useUnicode=true&useSSL=false&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true";
} else {
url = "jdbc:mysql://" + ip + ":" + port + "/pd_auth?serverTimezone=CTT&characterEncoding=utf8&useUnicode=true&useSSL=false&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true";
}
dto.setUrl(url);
add(dto);
DynamicDataSourceContextHolder.push(poolName);
return poolName;
}
private String wrapperPoolName(String merchantId) {
String uuid = UUID.randomUUID().toString().replace("-", "");
return merchantId.concat("_").concat(uuid);
}
public void add(DataSourceDTO dto) {
DataSourceProperty dataSourceProperty = new DataSourceProperty();
BeanUtils.copyProperties(dto, dataSourceProperty);
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
ds.addDataSource(dto.getPoolName(), dataSource);
}
}
实现原理
1. 节点: 进入impl实现之前设置数据源(可能需要先添加数据源); 离开impl之后 删除数据源
2. 新增数据源的实现: dynamic-datasource-spring-boot-starter 实现,操作简单
|