官网地址: https://shardingsphere.apache.org/
GitHub: https://github.com/apache/shardingsphere
官方示例:https://github.com/apache/shardingsphere/tree/master/examples
中文社区: https://community.sphere-ex.com/
背景: 项目用户数据库表量太大,对数据按月分表,需要满足如下需求:
- 将数据库按月分表;
- 自动建表;
- 数据自动跨表查询。
1.maven依赖
<!-- Sharding-JDBC -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.0</version>
</dependency>
<!-- ShardingJDBC 5.1.0使用druid连接池需要加dbcp依赖 -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>10.0.16</version>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<!-- Mybatis的分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
2.yml 配置
server:
port: 8099
spring:
### 处理连接池冲突 #####
main:
allow-bean-definition-overriding: true
shardingsphere:
# 是否启用 Sharding
enabled: true
# 打印sql
# props:
# sql-show: true
datasource:
names: ds0
ds0:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/szy-dev?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# 连接池的配置信息
# 初始化大小,最小,最大
initial-size: 5
min-idle: 5
maxActive: 100
# 配置获取连接等待超时的时间
maxWait: 10000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
rules:
sharding:
# 表策略配置
tables:
# sys_user_behavior 是逻辑表
sys_user_behavior:
# 配置数据节点,这里是按月分表
# 示例1:时间范围设置在202201 ~ 210012
# actualDataNodes: mydb.t_user_$->{2022..2100}0$->{1..9},mydb.t_user_$->{2022..2100}1$->{0..2}
# 示例2:时间范围设置在202201 ~ 202203
actualDataNodes: ds0.sys_user_behavior_$->{2023..2030}0$->{1..9},ds0.sys_user_behavior_$->{2023..2030}1$->{0..2}
tableStrategy:
# 使用标准分片策略
standard:
# 配置分片字段
shardingColumn: create_time
# 分片算法名称,不支持大写字母和下划线,否则启动就会报错
shardingAlgorithmName: time-sharding-algorithm
# 分片算法配置
shardingAlgorithms:
# 分片算法名称,不支持大写字母和下划线,否则启动就会报错
time-sharding-algorithm:
# 类型:自定义策略
type: CLASS_BASED
props:
# 分片策略
strategy: standard
# 分片算法类
algorithmClassName: com.demo.module.config.sharding.TimeShardingAlgorithm
# datasource:
# druid:
# stat-view-servlet:
# enabled: true
# loginUsername: admin
# loginPassword: 123456
# allow:
# web-stat-filter:
# enabled: true
# dynamic:
# druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
# # 连接池的配置信息
# # 初始化大小,最小,最大
# initial-size: 5
# min-idle: 5
# maxActive: 100
# # 配置获取连接等待超时的时间
# maxWait: 10000
# # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
# timeBetweenEvictionRunsMillis: 60000
# # 配置一个连接在池中最小生存的时间,单位是毫秒
# minEvictableIdleTimeMillis: 300000
# validationQuery: SELECT 1
# testWhileIdle: true
# testOnBorrow: false
# testOnReturn: false
# # 打开PSCache,并且指定每个连接上PSCache的大小
# poolPreparedStatements: true
# maxPoolPreparedStatementPerConnectionSize: 20
# # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
# filters: stat,wall,slf4j
# # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
# connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
# datasource:
# master:
# url: jdbc:mysql://localhost:3306/szy-dev?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
# username: root
# password: root
# driver-class-name: com.mysql.cj.jdbc.Driver
# # 多数据源配置
# #multi-datasource1:
# #url: jdbc:mysql://localhost:3306/jeecg-boot2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
# #username: root
# #password: root
# #driver-class-name: com.mysql.cj.jdbc.Driver
# mybatis-plus
mybatis-plus:
mapper-locations: classpath*:/mapper/*Mapper.xml
# 实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: com.demo.*.entity
# 测试环境打印sql
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
pagehelper:
helperDialect: postgresql
3.TimeShardingAlgorithm.java 分片算法类
package com.demo.module.config.sharding;
import com.demo.module.config.sharding.enums.ShardingTableCacheEnum;
import com.google.common.collect.Range;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import org.springframework.util.CollectionUtils;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
/**
* <p> @Title TimeShardingAlgorithm
* <p> @Description 分片算法,按月分片
*
* @author ACGkaka
* @date 2022/12/20 11:33
*/
@Slf4j
public class TimeShardingAlgorithm implements StandardShardingAlgorithm<Date> {
/**
* Date类型的分片时间格式
*/
private static final SimpleDateFormat TABLE_SHARD_Date_FORMATTER = new SimpleDateFormat("yyyyMM");
/**
* 分片时间格式
*/
private static final DateTimeFormatter TABLE_SHARD_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM");
/**
* 完整时间格式
*/
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
/**
* 完整时间格式
*/
private static final SimpleDateFormat DATE_TIME_FORMATTER_SPILE = new SimpleDateFormat("yyyy-MM-dd");
/**
* 表分片符号,例:t_user_202201 中,分片符号为 "_"
*/
private final String TABLE_SPLIT_SYMBOL = "_";
/**
* 精准分片
* @param tableNames 对应分片库中所有分片表的集合
* @param preciseShardingValue 分片键值,其中 logicTableName 为逻辑表,columnName 分片键,value 为从 SQL 中解析出来的分片键的值
* @return 表名
*/
@Override
public String doSharding(Collection<String> tableNames, PreciseShardingValue<Date> preciseShardingValue) {
String logicTableName = preciseShardingValue.getLogicTableName();
ShardingTableCacheEnum logicTable = ShardingTableCacheEnum.of(logicTableName);
createAllTable(logicTable, tableNames);
if (logicTable == null) {
log.error(">>>>>>>>>> 【ERROR】数据表类型错误,请稍后重试,logicTableNames:{},logicTableName:{}",
ShardingTableCacheEnum.logicTableNames(), logicTableName);
throw new IllegalArgumentException("数据表类型错误,请稍后重试");
}
/// 打印分片信息
log.info(">>>>>>>>>> 【INFO】精确分片,节点配置表名:{},数据库缓存表名:{}", tableNames, logicTable.resultTableNamesCache());
Date date = preciseShardingValue.getValue();
Instant instant = date.toInstant();
LocalDateTime localDateTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
String resultTableName = logicTableName + "_" + TABLE_SHARD_TIME_FORMATTER.format(localDateTime);
// 检查分表获取的表名是否存在,不存在则自动建表
if (!tableNames.contains(resultTableName)){
tableNames.add(resultTableName);
}
return ShardingAlgorithmTool.getShardingTableAndCreate(logicTable, resultTableName);
}
/**
* 范围分片
* @param tableNames 对应分片库中所有分片表的集合
* @param rangeShardingValue 分片范围
* @return 表名集合
*/
@Override
public Collection<String> doSharding(Collection<String> tableNames, RangeShardingValue<Date> rangeShardingValue) {
log.info("开始分表查询开始:{}",System.currentTimeMillis());
String logicTableName = rangeShardingValue.getLogicTableName();
ShardingTableCacheEnum logicTable = ShardingTableCacheEnum.of(logicTableName);
createAllTable(logicTable, tableNames);
if (logicTable == null) {
log.error(">>>>>>>>>> 【ERROR】逻辑表范围异常,请稍后重试,logicTableNames:{},logicTableName:{}",
ShardingTableCacheEnum.logicTableNames(), logicTableName);
throw new IllegalArgumentException("逻辑表范围异常,请稍后重试");
}
/// 打印分片信息
log.info(">>>>>>>>>> 【INFO】范围分片,节点配置表名:{},数据库缓存表名:{}", tableNames, logicTable.resultTableNamesCache());
// between and 的起始值
Range<Date> valueRange = rangeShardingValue.getValueRange();
boolean hasLowerBound = valueRange.hasLowerBound();
boolean hasUpperBound = valueRange.hasUpperBound();
// 获取最大值和最小值
Set<String> tableNameCache = logicTable.resultTableNamesCache();
String min = hasLowerBound ? String.valueOf(valueRange.lowerEndpoint()) : getLowerEndpoint(tableNameCache);
String max = hasUpperBound ? String.valueOf(valueRange.upperEndpoint()) : getUpperEndpoint(tableNameCache);
// 循环计算分表范围
Set<String> resultTableNames = new LinkedHashSet<>();
try {
Date minDate = DATE_TIME_FORMATTER_SPILE.parse(min);
Date maxDate = DATE_TIME_FORMATTER_SPILE.parse(max);
Calendar calendar = Calendar.getInstance();
while (minDate.before(maxDate) || minDate.equals(maxDate)) {
String tableName = logicTableName + TABLE_SPLIT_SYMBOL + TABLE_SHARD_Date_FORMATTER.format(minDate);
resultTableNames.add(tableName);
calendar.setTime(minDate); // 设置Calendar的时间为Date对象的时间
calendar.add(Calendar.DAY_OF_MONTH, 1); // 给日期加一天
minDate = calendar.getTime();
}
log.info("开始分表查询结束:{}",System.currentTimeMillis());
return ShardingAlgorithmTool.getShardingTablesAndCreate(logicTable, resultTableNames);
} catch (Exception e) {
return ShardingAlgorithmTool.getShardingTablesAndCreate(logicTable, logicTable.resultTableNamesCache());
}
}
@Override
public void init() {
}
@Override
public String getType() {
return null;
}
// --------------------------------------------------------------------------------------------------------------
// 私有方法
// --------------------------------------------------------------------------------------------------------------
/**
* 获取 最小分片值
* @param tableNames 表名集合
* @return 最小分片值
*/
private String getLowerEndpoint(Collection<String> tableNames) {
Optional<LocalDateTime> optional = tableNames.stream()
.map(o -> LocalDateTime.parse(o.replace(TABLE_SPLIT_SYMBOL, "") + "01 00:00:00", DATE_TIME_FORMATTER))
.min(Comparator.comparing(Function.identity()));
if (optional.isPresent()) {
ZonedDateTime zonedDateTime = optional.get().atZone(ZoneId.systemDefault());
Instant instant = zonedDateTime.toInstant();
return String.valueOf(Date.from(instant));
} else {
log.error(">>>>>>>>>> 【ERROR】获取数据最小分表失败,请稍后重试,tableName:{}", tableNames);
throw new IllegalArgumentException("获取数据最小分表失败,请稍后重试");
}
}
/**
* 获取 最大分片值
* @param tableNames 表名集合
* @return 最大分片值
*/
private String getUpperEndpoint(Collection<String> tableNames) {
Optional<LocalDateTime> optional = tableNames.stream()
.map(o -> LocalDateTime.parse(o.replace(TABLE_SPLIT_SYMBOL, "") + "01 00:00:00", DATE_TIME_FORMATTER))
.max(Comparator.comparing(Function.identity()));
if (optional.isPresent()) {
ZonedDateTime zonedDateTime = optional.get().atZone(ZoneId.systemDefault());
Instant instant = zonedDateTime.toInstant();
return String.valueOf(Date.from(instant));
} else {
log.error(">>>>>>>>>> 【ERROR】获取数据最大分表失败,请稍后重试,tableName:{}", tableNames);
throw new IllegalArgumentException("获取数据最大分表失败,请稍后重试");
}
}
/**
* 根据分片规则获取的表,创建所有的表
* @param logicTable
* @param tableNames
*/
private void createAllTable(ShardingTableCacheEnum logicTable, Collection<String> tableNames) {
if (!CollectionUtils.isEmpty(logicTable.resultTableNamesCache())) {
//如果缓存中有表了,则证明已经创建了表,无需再创建
return;
}
//根据分片规则创建表
ShardingAlgorithmTool.getShardingTablesAndCreate(logicTable,tableNames);
//刷新缓存
ShardingAlgorithmTool.tableNameCacheReload(logicTable);
}
}
4.ShardingAlgorithmTool.java 分片工具类
package com.demo.module.config.sharding;
import com.alibaba.druid.util.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.demo.module.config.sharding.enums.ShardingTableCacheEnum;
import com.demo.module.utils.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource;
import org.apache.shardingsphere.infra.config.RuleConfiguration;
import org.apache.shardingsphere.mode.manager.ContextManager;
import org.apache.shardingsphere.sharding.algorithm.config.AlgorithmProvidedShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.springframework.core.env.Environment;
import java.sql.*;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
/**
* <p> @Title ShardingAlgorithmTool
* <p> @Description 按月分片算法工具
*
* @author ACGkaka
* @date 2022/12/20 14:03
*/
@Slf4j
public class ShardingAlgorithmTool {
/** 表分片符号,例:sys_user_behavior_202201 中,分片符号为 "_" */
private static final String TABLE_SPLIT_SYMBOL = "_";
/** 数据库配置 */
private static final Environment ENV = SpringUtil.getApplicationContext().getEnvironment();
private static final String DATASOURCE_URL = ENV.getProperty("spring.shardingsphere.datasource.ds0.url");
private static final String DATASOURCE_USERNAME = ENV.getProperty("spring.shardingsphere.datasource.ds0.username");
private static final String DATASOURCE_PASSWORD = ENV.getProperty("spring.shardingsphere.datasource.ds0.password");
/**
* 检查分表获取的表名是否存在,不存在则自动建表
* @param logicTable 逻辑表
* @param resultTableNames 真实表名,例:sys_user_behavior_202201
* @return 存在于数据库中的真实表名集合
*/
public static Set<String> getShardingTablesAndCreate(ShardingTableCacheEnum logicTable, Collection<String> resultTableNames) {
return resultTableNames.stream().map(o -> getShardingTableAndCreate(logicTable, o)).collect(Collectors.toSet());
}
/**
* 检查分表获取的表名是否存在,不存在则自动建表
* @param logicTable 逻辑表
* @param resultTableName 真实表名,例:sys_user_behavior_202201
* @return 确认存在于数据库中的真实表名
*/
public static String getShardingTableAndCreate(ShardingTableCacheEnum logicTable, String resultTableName) {
// 缓存中有此表则返回,没有则判断创建
if (logicTable.resultTableNamesCache().contains(resultTableName)) {
return resultTableName;
} else {
// 未创建的表返回逻辑空表
boolean isSuccess = createShardingTable(logicTable, resultTableName);
return isSuccess ? resultTableName : logicTable.logicTableName();
}
}
/**
* 重载全部缓存
*/
public static void tableNameCacheReloadAll() {
Arrays.stream(ShardingTableCacheEnum.values()).forEach(ShardingAlgorithmTool::tableNameCacheReload);
}
/**
* 重载指定分表缓存
* @param logicTable 逻辑表,例:sys_user_behavior_202201
*/
public static void tableNameCacheReload(ShardingTableCacheEnum logicTable) {
// 读取数据库中所有表名
List<String> tableNameList = getAllTableNameBySchema(logicTable);
// 更新缓存、配置(原子操作)
logicTable.atomicUpdateCacheAndActualDataNodes(tableNameList);
// 删除旧的缓存(如果存在)
logicTable.resultTableNamesCache().clear();
// 写入新的缓存
logicTable.resultTableNamesCache().addAll(tableNameList);
// 动态更新配置 actualDataNodes
actualDataNodesRefresh(logicTable.logicTableName(), tableNameList);
}
/**
* 获取所有表名
* @return 表名集合
* @param logicTable 逻辑表
*/
public static List<String> getAllTableNameBySchema(ShardingTableCacheEnum logicTable) {
List<String> tableNames = new ArrayList<>();
if (StringUtils.isEmpty(DATASOURCE_URL) || StringUtils.isEmpty(DATASOURCE_USERNAME) || StringUtils.isEmpty(DATASOURCE_PASSWORD)) {
log.error(">>>>>>>>>> 【ERROR】数据库连接配置有误,请稍后重试,URL:{}, username:{}, password:{}", DATASOURCE_URL, DATASOURCE_USERNAME, DATASOURCE_PASSWORD);
throw new IllegalArgumentException("数据库连接配置有误,请稍后重试");
}
try (Connection conn = DriverManager.getConnection(DATASOURCE_URL, DATASOURCE_USERNAME, DATASOURCE_PASSWORD);
Statement st = conn.createStatement()) {
String logicTableName = logicTable.logicTableName();
try (ResultSet rs = st.executeQuery("show TABLES like '" + logicTableName + TABLE_SPLIT_SYMBOL + "%'")) {
log.info("查询数据库所有表:{}","show TABLES like '" + logicTableName + TABLE_SPLIT_SYMBOL + "%'");
while (rs.next()) {
String tableName = rs.getString(1);
log.info("分表格式:{}",String.format("^(%s\\d{6})$", logicTableName + TABLE_SPLIT_SYMBOL));
// 匹配分表格式 例:^(t\_contract_\d{6})$
if (org.apache.commons.lang3.StringUtils.isNotBlank(tableName) && tableName.matches(String.format("^(%s\\d{6})$", logicTableName + TABLE_SPLIT_SYMBOL))) {
tableNames.add(rs.getString(1));
}
}
}
} catch (SQLException e) {
log.error(">>>>>>>>>> 【ERROR】数据库连接失败,请稍后重试,原因:{}", e.getMessage(), e);
throw new IllegalArgumentException("数据库连接失败,请稍后重试");
}
return tableNames;
}
/**
* 动态更新配置 actualDataNodes
*
* @param logicTableName 逻辑表名
* @param tableNamesCache 真实表名集合
*/
public static void actualDataNodesRefresh(String logicTableName, List<String> tableNamesCache) {
try {
if (CollectionUtils.isEmpty(tableNamesCache)) {
return;
}
// 获取数据分片节点
String dbName = "ds0";
log.info(">>>>>>>>>> 【INFO】更新分表配置,logicTableName:{},tableNamesCache:{}", logicTableName, tableNamesCache);
// generate actualDataNodes
String newActualDataNodes = tableNamesCache.stream().map(o -> String.format("%s.%s", dbName, o)).collect(Collectors.joining(","));
ShardingSphereDataSource shardingSphereDataSource = SpringUtil.getBean(ShardingSphereDataSource.class);
updateShardRuleActualDataNodes(shardingSphereDataSource, logicTableName, newActualDataNodes);
}catch (Exception e){
log.error("初始化 动态表单失败,原因:{}", e.getMessage(), e);
}
}
// --------------------------------------------------------------------------------------------------------------
// 私有方法
// --------------------------------------------------------------------------------------------------------------
/**
* 刷新ActualDataNodes
*/
private static void updateShardRuleActualDataNodes(ShardingSphereDataSource dataSource, String logicTableName, String newActualDataNodes) {
// Context manager.
ContextManager contextManager = dataSource.getContextManager();
// Rule configuration.
String schemaName = "logic_db";
Collection<RuleConfiguration> newRuleConfigList = new LinkedList<>();
Collection<RuleConfiguration> oldRuleConfigList = dataSource.getContextManager()
.getMetaDataContexts()
.getMetaData(schemaName)
.getRuleMetaData()
.getConfigurations();
for (RuleConfiguration oldRuleConfig : oldRuleConfigList) {
if (oldRuleConfig instanceof AlgorithmProvidedShardingRuleConfiguration) {
// Algorithm provided sharding rule configuration
AlgorithmProvidedShardingRuleConfiguration oldAlgorithmConfig = (AlgorithmProvidedShardingRuleConfiguration) oldRuleConfig;
AlgorithmProvidedShardingRuleConfiguration newAlgorithmConfig = new AlgorithmProvidedShardingRuleConfiguration();
// Sharding table rule configuration Collection
Collection<ShardingTableRuleConfiguration> newTableRuleConfigList = new LinkedList<>();
Collection<ShardingTableRuleConfiguration> oldTableRuleConfigList = oldAlgorithmConfig.getTables();
oldTableRuleConfigList.forEach(oldTableRuleConfig -> {
if (logicTableName.equals(oldTableRuleConfig.getLogicTable())) {
ShardingTableRuleConfiguration newTableRuleConfig = new ShardingTableRuleConfiguration(oldTableRuleConfig.getLogicTable(), newActualDataNodes);
newTableRuleConfig.setTableShardingStrategy(oldTableRuleConfig.getTableShardingStrategy());
newTableRuleConfig.setDatabaseShardingStrategy(oldTableRuleConfig.getDatabaseShardingStrategy());
newTableRuleConfig.setKeyGenerateStrategy(oldTableRuleConfig.getKeyGenerateStrategy());
newTableRuleConfigList.add(newTableRuleConfig);
} else {
newTableRuleConfigList.add(oldTableRuleConfig);
}
});
newAlgorithmConfig.setTables(newTableRuleConfigList);
newAlgorithmConfig.setAutoTables(oldAlgorithmConfig.getAutoTables());
newAlgorithmConfig.setBindingTableGroups(oldAlgorithmConfig.getBindingTableGroups());
newAlgorithmConfig.setBroadcastTables(oldAlgorithmConfig.getBroadcastTables());
newAlgorithmConfig.setDefaultDatabaseShardingStrategy(oldAlgorithmConfig.getDefaultDatabaseShardingStrategy());
newAlgorithmConfig.setDefaultTableShardingStrategy(oldAlgorithmConfig.getDefaultTableShardingStrategy());
newAlgorithmConfig.setDefaultKeyGenerateStrategy(oldAlgorithmConfig.getDefaultKeyGenerateStrategy());
newAlgorithmConfig.setDefaultShardingColumn(oldAlgorithmConfig.getDefaultShardingColumn());
newAlgorithmConfig.setShardingAlgorithms(oldAlgorithmConfig.getShardingAlgorithms());
newAlgorithmConfig.setKeyGenerators(oldAlgorithmConfig.getKeyGenerators());
newRuleConfigList.add(newAlgorithmConfig);
}
}
// update context
contextManager.alterRuleConfiguration(schemaName, newRuleConfigList);
}
/**
* 创建分表
* @param logicTable 逻辑表
* @param resultTableName 真实表名,例:sys_user_behavior_202201
* @return 创建结果(true创建成功,false未创建)
*/
private static boolean createShardingTable(ShardingTableCacheEnum logicTable, String resultTableName) {
// 根据日期判断,当前月份之后分表不提前创建
String month = resultTableName.replace(logicTable.logicTableName() + TABLE_SPLIT_SYMBOL,"");
YearMonth shardingMonth = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyyMM"));
if (shardingMonth.isAfter(YearMonth.now())) {
return false;
}
synchronized (logicTable.logicTableName().intern()) {
// 缓存中有此表 返回
if (logicTable.resultTableNamesCache().contains(resultTableName)) {
return false;
}
// 缓存中无此表,则建表并添加缓存
executeSql(Collections.singletonList("CREATE TABLE IF NOT EXISTS `" + resultTableName + "` LIKE `" + logicTable.logicTableName() + "`;"));
// 缓存重载
tableNameCacheReload(logicTable);
}
return true;
}
/**
* 执行SQL
* @param sqlList SQL集合
*/
private static void executeSql(List<String> sqlList) {
if (StringUtils.isEmpty(DATASOURCE_URL) || StringUtils.isEmpty(DATASOURCE_USERNAME) || StringUtils.isEmpty(DATASOURCE_PASSWORD)) {
log.error(">>>>>>>>>> 【ERROR】数据库连接配置有误,请稍后重试,URL:{}, username:{}, password:{}", DATASOURCE_URL, DATASOURCE_USERNAME, DATASOURCE_PASSWORD);
throw new IllegalArgumentException("数据库连接配置有误,请稍后重试");
}
try (Connection conn = DriverManager.getConnection(DATASOURCE_URL, DATASOURCE_USERNAME, DATASOURCE_PASSWORD)) {
try (Statement st = conn.createStatement()) {
conn.setAutoCommit(false);
for (String sql : sqlList) {
st.execute(sql);
}
} catch (Exception e) {
conn.rollback();
log.error(">>>>>>>>>> 【ERROR】数据表创建执行失败,请稍后重试,原因:{}", e.getMessage(), e);
throw new IllegalArgumentException("数据表创建执行失败,请稍后重试");
}
} catch (SQLException e) {
log.error(">>>>>>>>>> 【ERROR】数据库连接失败,请稍后重试,原因:{}", e.getMessage(), e);
throw new IllegalArgumentException("数据库连接失败,请稍后重试");
}
}
}
5.ShardingTablesLoadRunner.java 初始化缓存类
package com.demo.module.config.sharding;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* <p> @Title ShardingTablesLoadRunner
* <p> @Description 项目启动后,读取已有分表,进行缓存
*
* @author ACGkaka
* @date 2022/12/20 15:41
*/
@Order(value = 1) // 数字越小,越先执行
@Component
public class ShardingTablesLoadRunner implements CommandLineRunner {
@Override
public void run(String... args) {
// 读取已有分表,进行缓存
ShardingAlgorithmTool.tableNameCacheReloadAll();
}
}
6.SpringUtil.java Spring工具类
package com.demo.module.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
/**
* <p> @Title SpringUtil
* <p> @Description Spring工具类
*
* @author ACGkaka
* @date 2022/12/20 14:39
*/
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtil.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return SpringUtil.applicationContext;
}
public static <T> T getBean(Class<T> cla) {
return applicationContext.getBean(cla);
}
public static <T> T getBean(String name, Class<T> cal) {
return applicationContext.getBean(name, cal);
}
public static String getProperty(String key) {
return applicationContext.getBean(Environment.class).getProperty(key);
}
}
7.DataSourceConfig类(动态数据源的时候配置,单数据源可不配)
package com.demo.module.config.sharding.DataSource;
/**
* <p> @Title DataSourceConfig
* <p> @Description 动态数据源配置(切换为sharding-jdbc数据源 => @DS(DataSourceConfig.SHARDING_DATA_SOURCE_NAME))
*
* @author ACGkaka
* @date 2022/12/21 16:01
*/
//@Configuration
//@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})
public class DataSourceConfig {
// /**
// * 分表数据源名称
// */
// public static final String SHARDING_DATA_SOURCE_NAME = "ds0";
// /**
// * 动态数据源配置项
// */
// @Autowired
// private DynamicDataSourceProperties properties;
//
// /**
// * sharding-jdbc有四种数据源,需要根据业务注入不同的数据源
// * <p>
// * 1.未使用分片, 脱敏的名称(默认): shardingDataSource;
// * 2.主从数据源: masterSlaveDataSource;
// * 3.脱敏数据源:encryptDataSource;
// * 4.影子数据源:shadowDataSource
// */
// @Lazy
// @Resource
// private AbstractDataSourceAdapter shardingDataSource;
//
// @Bean
// public DynamicDataSourceProvider dynamicDataSourceProvider() {
// Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
// return new AbstractDataSourceProvider() {
// @Override
// public Map<String, DataSource> loadDataSources() {
// Map<String, DataSource> dataSourceMap = createDataSourceMap(datasourceMap);
// // 将 shardingjdbc 管理的数据源也交给动态数据源管理
// dataSourceMap.put(SHARDING_DATA_SOURCE_NAME, shardingDataSource);
// return dataSourceMap;
// }
// };
// }
//
// /**
// * 将动态数据源设置为首选的
// * 当spring存在多个数据源时, 自动注入的是首选的对象
// * 设置为主要的数据源之后,就可以支持sharding-jdbc原生的配置方式了
// */
// @Primary
// @Bean
// public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
// DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
// dataSource.setPrimary(properties.getPrimary());
// dataSource.setStrict(properties.getStrict());
// dataSource.setStrategy(properties.getStrategy());
// dataSource.setProvider(dynamicDataSourceProvider);
// dataSource.setP6spy(properties.getP6spy());
// dataSource.setSeata(properties.getSeata());
// return dataSource;
// }
}
8.代码地址
ShardingSphereDemo: 使用sharding JDBC 单库按月份分表,自定义分片、自动创表 (gitee.com)