携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情 >>
哈喽,大家好,我是一条。
今天聊聊如何动态切换数据源,简单点说,就是一个服务配置多个数据库,在读写分离,分库分表都有应用。
假设有个用户表,,根据id水平分库分表,id为偶数,存在一个库,id为奇数存在另一个库,如何实现根据id查询用户详细的接口?
一块来看一下吧!
准备工作
1.一个 SpringBoot + Mybatis-Plus 的项目,我是用以前的练手项目改造的,本文不赘述建项目的过程。
2.准备两个库和用户表,sql 如下:
create table t_commerce_user
(
id bigint auto_increment comment '自增主键'
primary key,
username varchar(64) default '' not null comment '用户名',
password varchar(256) default '' not null comment 'MD5 加密之后的密码',
extra_info varchar(1024) default '' not null comment '额外的信息',
create_time datetime default '0000-01-01 00:00:00' not null comment '创建时间',
update_time datetime default '0000-01-01 00:00:00' not null comment '更新时间',
balance bigint default 0 not null comment '余额',
constraint username
unique (username)
)
comment '用户表_分表_1' charset = utf8;
INSERT INTO cloud_commerce_0.t_commerce_user (id, username, password, extra_info, create_time, update_time, balance) VALUES (2, 'test2', 'test', '{}', '2022-06-20 17:23:18', '2022-06-20 17:23:18', 0);
INSERT INTO cloud_commerce_0.t_commerce_user (id, username, password, extra_info, create_time, update_time, balance) VALUES (4, 'test4', 'test', '{}', '2022-06-20 17:23:18', '2022-06-20 17:23:18', 0);
INSERT INTO cloud_commerce_0.t_commerce_user (id, username, password, extra_info, create_time, update_time, balance) VALUES (6, 'test6', 'test', '{}', '2022-06-20 17:23:18', '2022-06-20 17:23:18', 0);
INSERT INTO cloud_commerce_0.t_commerce_user (id, username, password, extra_info, create_time, update_time, balance) VALUES (8, 'test8', 'test', '{}', '2022-06-20 17:23:18', '2022-06-20 17:23:18', 0);
INSERT INTO cloud_commerce_0.t_commerce_user (id, username, password, extra_info, create_time, update_time, balance) VALUES (10, 'test10', 'test', '{}', '2022-06-20 17:23:18', '2022-06-20 17:23:18', 0);
运行完成这个样子就行:
配置和依赖
Mybatis-Plus 的多数据源需要再添加一个依赖:
<!--动态数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
数据源配置
server:
port: 6001
servlet:
context-path: /commerce-user
spring:
application:
name: cloud-commerce-user
datasource:
# 动态数据源
dynamic:
primary: master
strict: false
datasource:
master:
url: jdbc:mysql://1.0.0.0:3306/cloud_commerce?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 123
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
slave0:
url: jdbc:mysql://1.0.0.0:3306/cloud_commerce_0?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 23
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
slave1:
url: jdbc:mysql://1.0.0.0:3306/cloud_commerce_1?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 123
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# 连接池
hikari:
maximum-pool-size: 8
minimum-idle: 4
idle-timeout: 30000
connection-timeout: 30000
max-lifetime: 45000
auto-commit: true
pool-name: EcommerceHikariCP
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:*/mapper/*.xml
master、slave0、slave1 可以自己定义名字。
基于注解的切换
Mybatis-Plus 提供了非常好用的注解来切换数据源,可以加在类或方法上。@DS("dsName")
UserService
做数据源策略和业务逻辑代码
@Service
@Slf4j
@RequiredArgsConstructor
public class UserService {
private final CommerceUserMapper userMapper;
private final UserDsService userDsService;
public CommerceUser getUserById(String id) {
return Long.parseLong(id) % 2 == 0
? userDsService.getUserByIdWithDbKey0(id)
: userDsService.getUserByIdWithDbKey1(id);
}
}
UserDsService
真正切换数据源的类
@Service
@Slf4j
@RequiredArgsConstructor
public class UserDsService {
private final CommerceUserMapper userMapper;
@DS("slave0")
public CommerceUser getUserByIdWithDbKey0(String id) {
return userMapper.selectById(id);
}
@DS("slave1")
public CommerceUser getUserByIdWithDbKey1(String id) {
return userMapper.selectById(id);
}
}
注意:这两个类一定要分开,类似事务注解,采用的代理,不分开会失效。
测试
启动项目,先看下日志的变化:
数据源都添加进来了,看看能不能切换呢?
查询个奇数,再查个偶数,都有结果,说明可以切换:
如何更优雅?
看到这是不是以为本文已经结束了。
现在思考,假如我们有10个库(夸张了有点),UserService 一共10个方法,都需要分库,以前只需要写10个方法,现在得多写100个方法,这也太不优雅了,这得把人逼疯。
怎么解决呢?
不写注解就好了,全部动态编码,不管多少库,还是一个service,只是加一段逻辑判断。
简单点说就是把注解做的事,我们在代码里自己写。
@DS做了什么
这里简单说下背后的原理。
首先,项目启动的时候,把配置文件里数据源配置加载进来,存在一个map里,key就是我们自定义的master、slave0。
再真正执行查询前,会有一个拦截器,把注解的value,也就是数据源的key,存储到一个ThreadLocal里,用栈存储。
获取数据库连接的时候,直接拿栈顶的数据集配置,这样就正好是我们配置的。
最后记得清空ThreadLocal,防止内存泄漏。
先看代码:
public CommerceUser getUserById(String id) {
DynamicDataSourceContextHolder.push(String.format("slave%s", Long.parseLong(id) % 2));
CommerceUser user = userMapper.selectById(id);
DynamicDataSourceContextHolder.clear();
return user;
}
简直太tm优雅了!
关于这块详细的源码分析参考:blog.csdn.net/labulaka24/…
点赞吧
\