【Sharding-JDBC 】【实战】----- SpringBoot3 整合 ShardingJDBC 做分库分表
·
文章目录
基于spring boot3 + lombok 1.18.30 + mybatis plus 3.5.5 +nacos
Spring Boot 3 整合 Sharding-JDBC 分库分表实现
根据需求,我们需要实现两个核心功能:
- 分库:将聊天记录库和用户业务库分离 (提高主业务吞吐量)
- 分表:聊天记录表按用户ID(deviceId)分为10个表 (保证单表不超过500万数据量)
下面是核心配置和业务代码实现:
Nacos配置中心配置
在Nacos中需要添加以下配置(dataId可以是application-dev.yml等):
spring:
shardingsphere:
# 数据源配置
datasource:
# 定义所有数据源名称,多个用逗号分隔
names: user_db,message_db
# 用户数据库配置
user_db:
type: com.zaxxer.hikari.HikariDataSource # 使用Hikari连接池
driver-class-name: com.mysql.cj.jdbc.Driver # MySQL驱动类
url: jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai # 数据库连接地址
username: root # 数据库用户名
password: root # 数据库密码
# 消息数据库配置
message_db:
type: com.zaxxer.hikari.HikariDataSource # 使用Hikari连接池
driver-class-name: com.mysql.cj.jdbc.Driver # MySQL驱动类
url: jdbc:mysql://localhost:3306/message_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai # 数据库连接地址
username: root # 数据库用户名
password: root # 数据库密码
# 分片规则配置
rules:
sharding:
# 表级别的分片配置
tables:
# 逻辑表名称:sys_message
sys_message:
# 实际数据节点:由数据库名+表名表达式组成,这里表示message_db库中的sys_message_0到sys_message_9表
actual-data-nodes: message_db.sys_message_${0..9}
# 表分片策略
table-strategy:
# 标准分片策略
standard:
sharding-column: deviceId # 分片键:使用deviceId字段进行分片
sharding-algorithm-name: sys_message_inline # 引用的分片算法名称
# 分片算法配置
sharding-algorithms:
# 与上面引用的算法名称保持一致
sys_message_inline:
type: INLINE # 算法类型:INLINE表示使用行表达式分片算法
props:
# 分片算法表达式:
# 1. 先计算deviceId的哈希值对10取模
# 2. 处理可能出现的负数情况(当哈希值为负数时,取模结果可能为负)
# 3. 确保最终结果在0-9之间,对应sys_message_0到sys_message_9表
algorithm-expression: sys_message_${deviceId.hashCode() % 10 + 10 >= 10 ? deviceId.hashCode() % 10 : deviceId.hashCode() % 10 + 10}
Sharding-JDBC 配置类
package com.zgb.config;
import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory;
import org.apache.shardingsphere.infra.config.algorithm.AlgorithmConfiguration;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.strategy.sharding.StandardShardingStrategyConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* Sharding-JDBC 配置类,用于配置分库分表规则
* 从Nacos配置中心获取数据源和分片规则配置
*/
@Configuration
public class ShardingJdbcConfig {
/**
* 用户业务库数据源配置,从Nacos获取
*/
@NacosValue("${spring.shardingsphere.datasource.user.url}")
private String userDbUrl;
@NacosValue("${spring.shardingsphere.datasource.user.username}")
private String userDbUsername;
@NacosValue("${spring.shardingsphere.datasource.user.password}")
private String userDbPassword;
@NacosValue("${spring.shardingsphere.datasource.user.driver-class-name}")
private String userDbDriverClassName;
/**
* 聊天记录库数据源配置,从Nacos获取
*/
@NacosValue("${spring.shardingsphere.datasource.message.url}")
private String messageDbUrl;
@NacosValue("${spring.shardingsphere.datasource.message.username}")
private String messageDbUsername;
@NacosValue("${spring.shardingsphere.datasource.message.password}")
private String messageDbPassword;
@NacosValue("${spring.shardingsphere.datasource.message.driver-class-name}")
private String messageDbDriverClassName;
/**
* 配置ShardingSphere数据源
*
* @return 配置好的ShardingSphere数据源
* @throws SQLException 数据库配置异常
*/
@Bean
public DataSource dataSource() throws SQLException {
// 1. 配置数据源
Map<String, DataSource> dataSources = new HashMap<>(2);
dataSources.put("user_db", createDataSource(userDbUrl, userDbUsername, userDbPassword, userDbDriverClassName));
dataSources.put("message_db", createDataSource(messageDbUrl, messageDbUsername, messageDbPassword, messageDbDriverClassName));
// 2. 配置分片规则
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
// 2.1 配置消息表分表规则
shardingRuleConfig.getTables().add(getSysMessageTableRuleConfiguration());
// 2.2 配置分库策略(按业务类型)
shardingRuleConfig.setDefaultDatabaseShardingStrategy(new StandardShardingStrategyConfiguration(
"database_type", "db_inline"));
// 2.3 配置分片算法
shardingRuleConfig.getShardingAlgorithms().put("table_inline",
new AlgorithmConfiguration("INLINE", getTableShardingAlgorithmProperties()));
shardingRuleConfig.getShardingAlgorithms().put("db_inline",
new AlgorithmConfiguration("INLINE", getDbShardingAlgorithmProperties()));
// 3. 创建并返回ShardingSphere数据源
return ShardingSphereDataSourceFactory.createDataSource(dataSources, shardingRuleConfig, new Properties());
}
/**
* 配置sys_message表的分表规则
*
* @return 消息表的分片规则配置
*/
private ShardingTableRuleConfiguration getSysMessageTableRuleConfiguration() {
ShardingTableRuleConfiguration tableRuleConfig = new ShardingTableRuleConfiguration("sys_message", "message_db.sys_message_${0..9}");
// 按deviceId进行分表
tableRuleConfig.setTableShardingStrategy(new StandardShardingStrategyConfiguration(
"deviceId", "table_inline"));
return tableRuleConfig;
}
/**
* 创建数据源
*
* @param url 数据库连接地址
* @param username 数据库用户名
* @param password 数据库密码
* @param driverClassName 数据库驱动类名
* @return 配置好的数据源
*/
private DataSource createDataSource(String url, String username, String password, String driverClassName) {
com.zaxxer.hikari.HikariConfig config = new com.zaxxer.hikari.HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setDriverClassName(driverClassName);
return new com.zaxxer.hikari.HikariDataSource(config);
}
/**
* 获取表分片算法属性配置
* 按deviceId哈希后取模10,分为10个表
*
* @return 表分片算法属性
*/
private Properties getTableShardingAlgorithmProperties() {
Properties props = new Properties();
props.setProperty("algorithm-expression", "sys_message_${deviceId.hashCode() % 10 + 10 >= 10 ? deviceId.hashCode() % 10 : deviceId.hashCode() % 10 + 10}");
return props;
}
/**
* 获取库分片算法属性配置
* 按业务类型区分用户库和消息库
*
* @return 库分片算法属性
*/
private Properties getDbShardingAlgorithmProperties() {
Properties props = new Properties();
props.setProperty("algorithm-expression", "${database_type == 'message' ? 'message_db' : 'user_db'}");
return props;
}
}
SysMessage 实体类
package com.zgb.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 人与AI对话消息实体类
* 对应分表后的sys_message_0到sys_message_9表
*/
@Data
@TableName(value = "sys_message", autoResultMap = true)
public class SysMessage {
/**
* 消息ID,主键,自增
*/
@TableId(type = IdType.AUTO)
private Long messageId;
/**
* 设备ID,用于分表的关键字段
*/
@TableField("deviceId")
private String deviceId;
/**
* 会话ID
*/
@TableField("sessionId")
private String sessionId;
/**
* 消息发送方:user-用户,assistant-人工智能
*/
private String sender;
/**
* AI扮演的角色ID
*/
private Long roleId;
/**
* 消息内容
*/
private String message;
/**
* 消息类型
*/
private String messageType;
/**
* 语音文件路径
*/
private String audioPath;
/**
* 状态:1-有效,0-删除
*/
private String state;
/**
* 消息发送时间
*/
private LocalDateTime createTime;
/**
* 数据库类型标识,用于分库的关键字段
* 不在数据库表中,仅用于分库路由
*/
@TableField(exist = false)
private String databaseType = "message";
}
mapper类
package com.zgb.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zgb.entity.SysMessage;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 消息表数据访问层接口
* 继承MyBatis Plus的BaseMapper,提供基本CRUD操作
*/
@Mapper
public interface SysMessageMapper extends BaseMapper<SysMessage> {
/**
* 根据设备ID查询消息列表
*
* @param deviceId 设备ID
* @return 该设备的消息列表
*/
List<SysMessage> selectByDeviceId(String deviceId);
/**
* 根据会话ID查询消息列表
*
* @param sessionId 会话ID
* @return 该会话的消息列表
*/
List<SysMessage> selectBySessionId(String sessionId);
/**
* 批量插入消息
*
* @param messages 消息列表
* @return 插入成功的记录数
*/
int batchInsert(List<SysMessage> messages);
}
服务接口类
package com.zgb.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zgb.entity.SysMessage;
import com.zgb.mapper.SysMessageMapper;
import com.zgb.service.SysMessageService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 消息服务实现类
* 实现消息的CRUD及相关业务操作
*/
@Service
@RequiredArgsConstructor
public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMessage> implements SysMessageService {
private final SysMessageMapper sysMessageMapper;
/**
* 保存消息
*
* @param message 消息实体,必须包含deviceId用于分表路由
* @return 保存成功返回true,否则返回false
*/
@Override
public boolean saveMessage(SysMessage message) {
// 设置数据库类型为message,确保路由到消息库
message.setDatabaseType("message");
return save(message);
}
/**
* 根据设备ID查询消息
*
* @param deviceId 设备ID,用于分表路由
* @return 该设备的消息列表
*/
@Override
public List<SysMessage> getMessagesByDeviceId(String deviceId) {
return sysMessageMapper.selectByDeviceId(deviceId);
}
/**
* 根据会话ID查询消息
* Sharding-JDBC会根据SQL中的deviceId进行路由
*
* @param sessionId 会话ID
* @return 该会话的消息列表
*/
@Override
public List<SysMessage> getMessagesBySessionId(String sessionId) {
return sysMessageMapper.selectBySessionId(sessionId);
}
/**
* 批量保存消息
*
* @param messages 消息列表,每条消息必须包含deviceId用于分表路由
* @return 保存成功的数量
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int batchSaveMessages(List<SysMessage> messages) {
// 设置数据库类型为message,确保路由到消息库
messages.forEach(message -> message.setDatabaseType("message"));
return sysMessageMapper.batchInsert(messages);
}
}
服务接口
package com.zgb.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zgb.entity.SysMessage;
import java.util.List;
/**
* 消息服务接口
* 提供消息的CRUD及相关业务操作
*/
public interface SysMessageService extends IService<SysMessage> {
/**
* 保存消息
*
* @param message 消息实体
* @return 保存成功返回true,否则返回false
*/
boolean saveMessage(SysMessage message);
/**
* 根据设备ID查询消息
*
* @param deviceId 设备ID
* @return 消息列表
*/
List<SysMessage> getMessagesByDeviceId(String deviceId);
/**
* 根据会话ID查询消息
*
* @param sessionId 会话ID
* @return 消息列表
*/
List<SysMessage> getMessagesBySessionId(String sessionId);
/**
* 批量保存消息
*
* @param messages 消息列表
* @return 保存成功的数量
*/
int batchSaveMessages(List<SysMessage> messages);
}
实现说明
-
分库策略:
- 通过
databaseType字段区分,聊天记录相关表路由到message_db,用户业务表路由到user_db - 在消息实体类中默认设置
databaseType = "message",确保消息表操作都路由到消息库
- 通过
-
分表策略:
- 聊天记录表
sys_message按deviceId的哈希值取模10分为10个表(sys_message_0到sys_message_9) - 使用INLINE算法实现分片,确保相同设备的消息保存在同一个表中,便于查询
- 聊天记录表
-
核心优势:
- 分片规则配置在Nacos中,可动态调整,无需重启服务
- 使用MyBatis Plus简化数据库操作,开发者无需关心具体操作的是哪个分表
- 分库分表逻辑对业务代码透明,降低开发复杂度
以上实现满足了将聊天记录与用户业务分离存储,并按用户ID水平拆分聊天记录表的需求,适合高并发、大数据量的聊天场景。
更多推荐


所有评论(0)