版本
springcloud版本+sharding-jdbc版本
- springcloud版本:2021.0.5
- springcloudalibaba版本:2021.0.6.0
- shardingsphere版本:5.2.1
- springboot版本:2.6.13
- mysql版本:8.0.30
- pom配置如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.13</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud-alibaba.version>2021.0.6.0</spring-cloud-alibaba.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
<jackson.version>2.16.1</jackson.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.2.1</version>
<exclusions>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.32</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
配置
server:
port: 8082
shutdown: graceful
spring:
application:
name: test-api
filter:
patterns: /*
main:
allow-bean-definition-overriding: true
lifecycle:
timeout-per-shutdown-phase: 40s
cloud:
loadbalancer:
ribbon:
enabled: false
cache:
enabled: true
ttl: 5s
capacity: 256
nacos:
discovery:
enabled: true
register-enabled: true
server-addr:127.0.0.1
username: nacos
password: nacos
group: group_center_1
service: ${spring.application.name}
namespace: test
metadata:
mgp:
system: test
version: 1.0
cache:
enabled: true
ttl: 5s
capacity: 256
shardingsphere:
datasource:
names: test,test_slave,test_00,test_00_slave,test_01,test_01_slave
test:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
password: huyg32
type: com.zaxxer.hikari.HikariDataSource
username: huyg32
test_slave:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3307/test?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
password: huyg32
type: com.zaxxer.hikari.HikariDataSource
username: huyg32
test_00:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/test_00?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
password: huyg32
type: com.zaxxer.hikari.HikariDataSource
username: huyg32
test_00_slave:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3307/test_00?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
password: huyg32
type: com.zaxxer.hikari.HikariDataSource
username: huyg32
test_01:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/test_01?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
password: huyg32
type: com.zaxxer.hikari.HikariDataSource
username: huyg32
test_01_slave:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3307/test_01?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
password: huyg32
type: com.zaxxer.hikari.HikariDataSource
username: huyg32
props:
sql:
show: true
rules:
readwrite-splitting:
data-sources:
test:
static-strategy:
write-data-source-name: test
read-data-source-names: test_slave
load-balancer-name: round_robin
test_00:
static-strategy:
write-data-source-name: test_00
read-data-source-names: test_00_slave
load-balancer-name: round_robin
test_01:
static-strategy:
write-data-source-name: test_01
read-data-source-names: test_01_slave
load-balancer-name: round_robin
load-balancers:
round_robin:
type: ROUND_ROBIN
sharding:
sharding-algorithms:
all_database_alg:
type: CLASS_BASED
props:
strategy: COMPLEX
algorithmClassName: 自定义类3
all_table_alg:
type: CLASS_BASED
props:
strategy: COMPLEX
algorithmClassName: 自定义类4
default-data-source-name: test
default-table-strategy:
complex:
sharding-columns: id
sharding-algorithm-name: all_table_alg
tables:
shop_order:
actual-data-nodes: test_$->{(0..1).collect{t->t.toString().padLeft(2,'0')}}.shop_order_$->{2024..2025}
database-strategy:
complex:
sharding-columns: tenant_code,id
sharding-algorithm-name: all_database_alg
table-strategy:
complex:
sharding-columns: id
sharding-algorithm-name: all_table_alg
mybatis-plus:
mapper-locations: classpath*:mapping/**/*Mapper.xml
global-config:
field-strategy: 2
db-column-underline: true
refresh-mapper: true
logic-delete-value: 0
logic-not-delete-value: 1
db-config:
db-type: mysql
id-type: assign_id
logic-delete-value: 1
logic-not-delete-value: 0
table-underline: true
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- 分库策略:分库这边采用的是租户的方式进行分库,给定一个租户hash取模(分几个库和几取模),例如租户号T20241020100001.hashCode()%2
- 分表策略:按照ID进行分表,ID的生成策略为按照年月+分库+10位雪花算法/年月日+10位雪花算法
- 主键生成策略
package org.example.config;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
@Slf4j
public class 类名自定义 {
private static final Integer DATA_BASE_SIZE = 2;
public static Long getDataIdWithTenantCode(String tenantCode){
int tenantCodeMod = Math.abs(tenantCode.hashCode() % DATA_BASE_SIZE);
String dataSource = String.format("%02d",tenantCodeMod);
String date = DateUtil.format(new Date(), "yyyyMM");
String snowFlakeId = IdUtil.getSnowflake().nextIdStr();
log.info("生成的雪花算法ID:{}",snowFlakeId);
String tenBitSnowFlakeId = snowFlakeId.substring(0, 10);
return Long.parseLong(date+dataSource+tenBitSnowFlakeId);
}
public static Long getDataIdWithoutTenantCode() {
String date = DateUtil.format(new Date(), "yyyyMMdd");
String snowFlakeId = IdUtil.getSnowflake().nextIdStr();
log.info("生成的雪花算法ID:{}",snowFlakeId);
String tenBitSnowFlakeId = snowFlakeId.substring(0, 10);
return Long.parseLong(date+tenBitSnowFlakeId);
}
}
package org.example.config;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Slf4j
@Component
@Primary
public class 类名自定义2 implements IdentifierGenerator {
String tenantCodeStr = "";
@Override
public Number nextId(Object entity) {
try {
Object tenantCode = entity.getClass().getMethod("getTenantCode").invoke(entity);
if (Objects.nonNull(tenantCode)){
tenantCodeStr = tenantCode.toString();
}
}catch (Exception e){
if (log.isDebugEnabled()) {
log.warn("插入实体表没有租户信息{}",entity,e);
}
}
if (StringUtils.isNotBlank(tenantCodeStr)){
return PrcIdGenerate.getDataIdWithTenantCode(tenantCodeStr);
}
return 类名自定义.getDataIdWithoutTenantCode();
}
}
package org.example.config;
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Range;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingValue;
import java.util.*;
public class 类名自定义3 implements ComplexKeysShardingAlgorithm<String> {
private Properties props;
private static final String TENANT_CODE = "tenant_code";
private static final String ID = "id";
private static final int DATA_BASE_SIZE = 2;
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<String> shardingValues) {
Map<String,Collection<String>> columnNameAndShardingValuesMap = shardingValues.getColumnNameAndShardingValuesMap();
Map<String, Range<String>> columnNameAndRangeValuesMap = shardingValues.getColumnNameAndRangeValuesMap();
if (ObjectUtils.isNotEmpty(columnNameAndShardingValuesMap)){
return this.doColumnNameAndSharding(availableTargetNames,columnNameAndShardingValuesMap);
}
if (ObjectUtils.isNotEmpty(columnNameAndRangeValuesMap)){
return this.doColumnNameAndRange(availableTargetNames,columnNameAndRangeValuesMap);
}
throw new UnsupportedOperationException();
}
private Collection<String> doColumnNameAndRange(Collection<String> availableTargetNames, Map<String, Range<String>> columnNameAndRangeValuesMap) {
if (columnNameAndRangeValuesMap.containsKey(TENANT_CODE)) {
throw new UnsupportedOperationException("落库分片键" + TENANT_CODE + "不支持范围分片");
}
if (columnNameAndRangeValuesMap.containsKey(ID)) {
throw new UnsupportedOperationException("落库分片键" + ID + "不支持范围分片");
}
return availableTargetNames;
}
private Collection<String> doColumnNameAndSharding(Collection<String> availableTargetNames, Map<String, Collection<String>> columnNameAndShardingValuesMap) {
if (columnNameAndShardingValuesMap.containsKey(TENANT_CODE)){
Collection<String> tenantCodeValueCol = columnNameAndShardingValuesMap.get(TENANT_CODE);
Set<String> tenantModList = new HashSet<>();
Set<String> tenantShardingList = new HashSet<>();
Iterator<String> tenantCodeColIterator = tenantCodeValueCol.iterator();
while(tenantCodeColIterator.hasNext()){
String tenantCode = tenantCodeColIterator.next();
int tenantCodeMod = Math.abs(tenantCode.hashCode() % DATA_BASE_SIZE);
String tenantCodeModStr = String.format("%02d", tenantCodeMod);
tenantModList.add(tenantCodeModStr);
}
for (String availableTargetName: availableTargetNames){
String availableTargetMod = availableTargetName.substring(availableTargetName.length() - 2);
if (tenantModList.contains(availableTargetMod)){
tenantShardingList.add(availableTargetName);
}
}
if (CollUtil.isNotEmpty(tenantShardingList)){
return tenantShardingList;
}
}
if (columnNameAndShardingValuesMap.containsKey(ID)){
Set<String> idModeList = new HashSet<>();
Set<String> idShardingList = new HashSet<>();
Collection<String> idCol = columnNameAndShardingValuesMap.get(ID);
Iterator<String> idColIterator = idCol.iterator();
while(idColIterator.hasNext()){
String id = String.valueOf(idColIterator.next());
String idMod = id.substring(6, 8);
idModeList.add(idMod);
}
for (String availableTargetName: availableTargetNames){
String availableTargetMod = availableTargetName.substring(availableTargetName.length() - 2);
if (idModeList.contains(availableTargetMod)){
idShardingList.add(availableTargetName);
}
}
if (CollUtil.isNotEmpty(idShardingList)){
return idShardingList;
}
}
return availableTargetNames;
}
@Override
public Properties getProps() {
return props;
}
@Override
public void init(Properties properties) {
this.props = properties;
}
}
package org.example.config;
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Lists;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingValue;
import org.example.exception.BusinessException;
import java.util.*;
public class 类名自定义4 implements ComplexKeysShardingAlgorithm<Long> {
private Properties props;
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<Long> shardingValues) {
Collection<String> result = new HashSet<>();
Map<String, Collection<Long>> columnNameAndShardingValuesMap = shardingValues.getColumnNameAndShardingValuesMap();
if (CollUtil.isNotEmpty(columnNameAndShardingValuesMap)){
for (Collection<Long> ids: columnNameAndShardingValuesMap.values()){
for (Long id: ids){
String shardingKey = columnNameAndShardingValuesMap.keySet().iterator().next();
if (columnNameAndShardingValuesMap.containsKey(shardingKey)){
String keyValue = String.valueOf(id);
result.addAll(getTargetNamesById(availableTargetNames, keyValue, shardingValues.getLogicTableName()));
}
}
}
}
if (CollUtil.isNotEmpty(result)){
return result;
}
return availableTargetNames;
}
private Collection<String> getTargetNamesById(Collection<String> availableTargetNames, String keyValue, String logicTableName) {
if (keyValue.length()<=6){
throw new BusinessException("00001","业务ID异常,请删除历史数据重新生成");
}
String substr = keyValue.substring(0, 6);
for (String availableTargetName : availableTargetNames) {
if (availableTargetName.endsWith(substr) && availableTargetName.startsWith(logicTableName)) {
return Lists.newArrayList(availableTargetName);
}
}
substr = keyValue.substring(0, 4);
for (String availableTargetName : availableTargetNames) {
if (availableTargetName.endsWith(substr) && availableTargetName.startsWith(logicTableName)) {
return Lists.newArrayList(availableTargetName);
}
}
return availableTargetNames;
}
@Override
public Properties getProps() {
return props;
}
@Override
public void init(Properties properties) {
this.props = properties;
}
}
测试
动态数据源的实现