在前面的内容中,我们集成使用的ShardingSphere Spring Boot是4.1.1的版本,这个版本在20年就已经停止更新了,而比较新的版本为5.1.2。这个版本也在2022年已经停止更新了,这个版本的配置在官网上的配置和现实的差距比较大,使用官网的配置只能进行简单根据取模等实现简单的分库分表,无法实现自定义的分库分表。而使用自定义的分库分表配置时,使用官网的配置总是报错,根据多次试错,得出了一个比较合理的配置,特此记录。该demo是在之前的系列文章里面更新的,有些代码可参考之前的系列
Maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<!-- 由于Mybatis Plus使用了druid作为默认的数据源,分库分表使用的是HikariDataSource作为数据源管理,所以需要排除druid的starter -->
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 分库分表,先去掉druid数据源 -->
<!-- <dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.18</version>
</dependency>-->
<!-- web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.16</version>
</dependency>
<!-- Mybatis与JacksonJson时间转换异常处理 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
<version>1.0.1</version>
</dependency>
<!-- ES依赖 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<exclusions>
<exclusion>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</exclusion>
</exclusions>
<version>${elasticsearch.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch
-->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- mybatis-plus 依赖 -->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- mybatis plus 解决sharding+动态数据源启动报错:Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- mybatis-plus 代码生成器依赖 -->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- freemarker 依赖 -->
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>ft-contract</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- 旧版本分库分表 -->
<!-- <dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>-->
<!-- 5.1.2版本分库分表 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
YML配置
主application配置:
server:
port: 8081
spring:
# sharding-jdbc配置
shardingsphere:
datasource:
names: ftdb0,ftdb1
ftdb0:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://localhost:3306/ftdb0?useUnicode=true&autoReconnect=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: xxx
ftdb1:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://localhost:3306/ftdb1?useUnicode=true&autoReconnect=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: xxx
rules:
sharding:
key-generators: # 配置分布式主键
snowflake:# 雪花算法主键配置
type: SNOWFLAKE
props:
worker-id: 1034 # 工作Id不能大于1024
datacenter-id: 895623
max-tolerate-time-difference-milliseconds: 2000 # 时钟回拨最大容忍时间(单位:毫秒),默认值为:0
max-vibrate-offset: 10 # 最大抖动数,默认值为:0
props:
sql-show: true # 是否开启SQL显示,默认值: false
application:
name: ft-server
profiles:
active: sharding-core
# active: sharding-jdbc,master-slave
# dynamic数据源配置
datasource:
dynamic:
primary: master0 # 设置默认的数据源或者数据源组,默认值即为master
datasource:
master0:
driver-class-name: com.mysql.cj.jdbc.Driver # mysql版本为5.x使用该配置: com.mysql.jdbc.Driver,8.0版本使用:com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ftdb1?useUnicode=true&autoReconnect=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
username: root
password: xxx
master1:
driver-class-name: com.mysql.cj.jdbc.Driver # mysql版本为5.x使用该配置: com.mysql.jdbc.Driver,8.0版本使用:com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ftdb0?useUnicode=true&autoReconnect=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
username: root
password: xxx
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
分库分表application-sharding-core.yml配置:
spring:
shardingsphere:
rules:
sharding:
tables:
#测试自定义标准分片策略
c_order:
actual-data-nodes: ftdb$->{0..1}.c_order$->{2023..2024}
database-strategy:
standard:
sharding-column: delivery_time
sharding-algorithm-name: test_range_algorithm_db
table-strategy:
standard:
sharding-column: delivery_time
sharding-algorithm-name: test_range_algorithm_table
key-generate-strategy:
column: id
key-generator-name: SNOWFLAKE
#测试自定义复合分片策略
b_order:
actual-data-nodes: ftdb$->{0..1}.b_order$->{0..2}
database-strategy:
complex:
sharding-columns: company_id,position_id
sharding-algorithm-name: complex_sharding_algorithm_db
table-strategy:
complex:
sharding-columns: company_id,position_id
sharding-algorithm-name: complex_sharding_algorithm_table
key-generate-strategy:
column: id
key-generator-name: MYKEY #自定义主键策略
sharding-algorithms:
test_range_algorithm_db:
type: CLASS_BASED_DB
strategy: STANDARD
algorithm-class-name: com.example.ftserver.sharding.shardingalgrithm.range.FtDataBaseRangeShardingAlgorithm
test_range_algorithm_table:
type: CLASS_BASED_TABLE
strategy: STANDARD
algorithm-class-name: com.example.ftserver.sharding.shardingalgrithm.range.FtTableRangeShardingAlgorithm
complex_sharding_algorithm_db:
type: CLASS_BASED_COMPLEX_DB
strategy: COMPLEX
algorithm-class-name: com.example.ftserver.sharding.shardingalgrithm.complex.FtDataBaseComplexShardingAlgorithm
complex_sharding_algorithm_table:
type: CLASS_BASED_COMPLEX_TABLE
strategy: COMPLEX
algorithm-class-name: com.example.ftserver.sharding.shardingalgrithm.complex.FtTableComplexShardingAlgorithm
分库的application-sharding-core.yml配置需要特别注意的是shardingsphere的拼法,在IDEA中会提示该词拼写错误,应该使用sharding-sphere。如果按照IDEA的提示改为sharding-sphere后,在项目启动时,读取配置文件的key配置是shardingsphere,会报找不到type的报错。具体信息如图所示:
IDEA中的提示:
ShardingAlgorithmProvidedBeanRegistry类中注册Bean的key:
点击ShardingAlgorithmProvidedBeanRegistry类的registerBean方法跳转到AbstractAlgorithmProvidedBeanRegistry类,最终的createAlgorithmConfiguration()方法中返回的type会为null,在后面的ShardingSphereAlgorithmConfiguration构建中会检查type的类型,如果为空时会抛出异常:
ShardingSphereAlgorithmConfiguration检查null:
多数据源配置类
将之前的Mybatis Plus的多数据源的版本更新到3.5.2,之前demo的多数据源需要重新配置。
多数据源配置类:
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource")
// 使用lombok的构造器注入时,需要使用@RequiredArgsConstructor注解,且变量必须用final或者@NonNull,@NotNull注解修饰
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class ShardingDataSourceConfig {
@NonNull // lombok非空注解
private DynamicDataSourceProperties properties;
@Lazy
@NotNull // org.jetbrains.annotations非空注解
private DataSource shardingDataSource;
@Primary
@Bean("shardingDataSource")
public DataSource dataSource() {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setPrimary(properties.getPrimary());
dataSource.setStrict(properties.getStrict());
dataSource.setStrategy(properties.getStrategy());
dataSource.setP6spy(properties.getP6spy());
dataSource.setSeata(properties.getSeata());
dataSource.addDataSource("sharding", shardingDataSource);
return dataSource;
}
}
Mybatis Plus配置类信息,去掉之前的多数据源配置信息
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.example.ftserver.datapermission.MyDataPermission;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author tencent wb
* @date 2023-09-27 17:54
* @description
*/
@Configuration
@MapperScan(basePackages = "com.example.ftserver.mapper")
// 需要配置自动注入设置,不然无法使用,SpringBootConfiguration.class是位于 org.apache.shardingsphere.shardingjdbc.spring.boot.包下的类
//@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})
//@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, ShardingSphereAutoConfiguration.class})
public class MybatisPlusConfig {
/**
* 自定义SQL注入拦截器
*
* @return
*/
@Bean
public CustomizedSqlInjector sqlInjector() {
return new CustomizedSqlInjector();
}
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加数据权限
interceptor.addInnerInterceptor(new MyDataPermission());
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加
return interceptor;
}
}
自定义分库分表类更新
标准分片分库分表,将之前的分片改为继承StandardShardingAlgorithm类,并重写getType方法,该方法返回的值需要与配置文件中的sharding-algorithms下的xx中的type的值一致:
标准分表算法:
package com.example.ftserver.sharding.shardingalgrithm.range;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
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.stereotype.Component;
import java.util.Collection;
import java.util.Properties;
/**
* @author zhangxin
* @description: 表范围分片算法,需要配置精确分片算法,且这两者的参数需要一致
*/
@Component
@Slf4j
public class FtTableRangeShardingAlgorithm extends AbstractRangSharding<Long> implements StandardShardingAlgorithm<Comparable<?>> {
private Properties props;
@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Comparable<?>> rangeShardingValue) {
Collection<String> dataSource = super.rangeTargetDataSource(rangeShardingValue, true);
return CollectionUtils.isEmpty(dataSource) ? collection : dataSource;
}
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Comparable<?>> preciseShardingValue) {
String dataSource = super.preciseTargetDataSource(preciseShardingValue, true);
// 为空时使用默认数据表
return StringUtils.isEmpty(dataSource) ? "c_order2023" : dataSource;
}
@Override
public String getType() {
return "CLASS_BASED_TABLE";
}
@Override
public Properties getProps() {
return props;
}
@Override
public void init(Properties properties) {
this.props = properties;
}
}
标准分库算法:
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
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.stereotype.Component;
import java.util.Collection;
import java.util.Properties;
@Component
@Slf4j
public class FtDataBaseRangeShardingAlgorithm extends AbstractRangSharding<Long> implements StandardShardingAlgorithm<Comparable<?>> {
private Properties props;
@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Comparable<?>> rangeShardingValue) {
Collection<String> dataSource = super.rangeTargetDataSource(rangeShardingValue, false);
return CollectionUtils.isEmpty(dataSource) ? collection : dataSource;
}
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Comparable<?>> preciseShardingValue) {
String dataSource = super.preciseTargetDataSource(preciseShardingValue, false);
return StringUtils.isEmpty(dataSource) ? "ftdb0" : dataSource;
}
@Override
public Properties getProps() {
return props;
}
@Override
public void init(Properties properties) {
this.props = properties;
}
@Override
public String getType() {
return "CLASS_BASED_DB";
}
}
在标准分库中,该示例的demo的数据库type配置示例如图所示,图中的test_range_algorithm_table为分表配置:
自定义复合分库分表算法时,将之前的改为继承ComplexKeysShardingAlgorithm类,并重写getType方法,该方法返回的值需要与配置文件中的sharding-algorithms下的xx中的type的值一致
自定义复合分库算法:
import com.example.ftserver.sharding.shardingalgrithm.AbstractComplexSharding;
import com.example.ftserver.sharding.sharingvalue.ShardingParam;
import com.example.ftserver.sharding.sharingvalue.ShardingRangeConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingValue;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
@Component
@Slf4j
public class FtDataBaseComplexShardingAlgorithm extends AbstractComplexSharding<Long> implements ComplexKeysShardingAlgorithm<Comparable<?>> {
private Properties props;
/**
* 根据复杂的键进行分片操作。
*
* @param collection 需要进行分片操作的集合。
* @param complexKeysShardingValue 复杂键分片值,包含逻辑表名和分片参数。
* @return 分片后的数据集合。如果无法进行分片或分片结果为空,则返回原始集合。
*/
@Override
public Collection<String> doSharding(Collection<String> collection, ComplexKeysShardingValue<Comparable< ? >> complexKeysShardingValue) {
// 获取逻辑表名
String logicTableName = complexKeysShardingValue.getLogicTableName();
// 初始化分片参数
List<ShardingParam> sharingParams = super.complexKeyInitSharingParams(complexKeysShardingValue);
if (CollectionUtils.isEmpty(sharingParams)) {
return collection;
}
List<ShardingRangeConfig> rangeConfigs = super.shardingRangeConfigs(false,logicTableName);
// 根据分片参数和范围配置获取目标数据源
Collection<String> dataSource = super.getTargetDataSource(sharingParams, rangeConfigs);
return CollectionUtils.isEmpty(dataSource) ? collection : dataSource;
}
@Override
public String getType() {
return "CLASS_BASED_COMPLEX_DB";
}
@Override
public Properties getProps() {
return props;
}
@Override
public void init(Properties properties) {
this.props = properties;
}
}
自定义复合分片分表算法
package com.example.ftserver.sharding.shardingalgrithm.complex;
import com.example.ftserver.sharding.shardingalgrithm.AbstractComplexSharding;
import com.example.ftserver.sharding.sharingvalue.ShardingParam;
import com.example.ftserver.sharding.sharingvalue.ShardingRangeConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingValue;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
@Component
@Slf4j
public class FtTableComplexShardingAlgorithm extends AbstractComplexSharding<Long> implements ComplexKeysShardingAlgorithm<Comparable<?>> {
private Properties props;
/**
* 根据复杂的键进行分片操作。
*
* @param collection 需要进行分片操作的集合。
* @param complexKeysShardingValue 复杂键分片值,包含逻辑表名和分片键值信息。
* @return 分片后的数据集合。如果无法进行分片或分片结果为空,则返回原始集合。
*/
@Override
public Collection<String> doSharding(Collection<String> collection, ComplexKeysShardingValue<Comparable<?>> complexKeysShardingValue) {
List<ShardingParam> sharingParams = super.complexKeyInitSharingParams(complexKeysShardingValue);
if (CollectionUtils.isEmpty(sharingParams)) {
return collection;
}
String logicTableName = complexKeysShardingValue.getLogicTableName();
List<ShardingRangeConfig> rangeConfigs = super.shardingRangeConfigs(true, logicTableName);
// 根据分片参数和范围配置获取目标数据源
Collection<String> dataSource = super.getTargetDataSource(sharingParams, rangeConfigs);
return CollectionUtils.isEmpty(dataSource) ? collection : dataSource;
}
@Override
public String getType() {
return "CLASS_BASED_COMPLEX_TABLE";
}
@Override
public Properties getProps() {
return props;
}
@Override
public void init(Properties properties) {
this.props = properties;
}
}
SPI配置
在算法完成后,需要配置到SPI中,在resources目录下新建META-INF文件夹,再新建services文件夹,然后新建文件的名字为org.apache.shardingsphere.sharding.spi.ShardingAlgorithm【META-INF/services/org.apache.shardingsphere.sharding.spi.ShardingAlgorithm】,文件内容为分库分表算法的全限定类名
如图所示:
自定义的主键策略
由于更新了版本,所以需要更新自定义的主键策略配置。具体步骤如下:
1.新建一个类,继承KeyGenerateAlgorithm,并重写相关方法:
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicLong;
@Slf4j
public class MyShardingKeyGenerator implements KeyGenerateAlgorithm {
private final AtomicLong atomicLong = new AtomicLong(0);
private Properties properties;
@Override
public Comparable<?> generateKey() {
log.info("generateKey 开始生成");
Long id = atomicLong.incrementAndGet();
log.info("generateKey 生成结束,生成结果:{}", id);
// 这里可以根据自己的业务需求生成不同的key
return id;
}
@Override
public String getType() {
// 这里需要和配置文件中属性 sharding.tables. key-generate-strategy.key-generator-name值一致
return "MYKEY";
}
@Override
public Properties getProps() {
return properties;
}
@Override
public void init(Properties properties) {
this.properties = properties;
}
}
2.然后在META-INF/services包下面新建文件的名字为org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm的文件【META-INF/services/org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm】,文件内容为自定义主键生成策略的全限定类名
如图所示:
结语
**注意点:在表的Mapper类,或者service类上面,需要配置@DS()注解,该注解的值需要与DynamicRoutingDataSource 中的addDataSource方法的名称一致。**该demo中配置示例如图:
其实其配置很简单,但是缺乏相关的文献,网上资料也是混乱不堪。需要多看看源码,看哪里有错误,修正配置信息。
附录:
一个自定义的雪花算法工具类,用于计算雪花Id生成的时间:
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.algorithm.keygen.SnowflakeKeyGenerateAlgorithm;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* SnowflakeUtil 类实现了 InitializingBean 接口,
* 提供了一个基于 Snowflake 算法的 ID 生成器。
*/
@Component
@PropertySource("classpath:application.yml")
@Slf4j
public class SnowflakeUtil implements InitializingBean {
@Value("${spring.shardingsphere.rules.sharding.key-generators.snowflake.props.worker-id}")
private long workerId;
@Value("${spring.shardingsphere.rules.sharding.key-generators.snowflake.props.datacenter-id}")
private long datacenterId;
@Value("${spring.shardingsphere.rules.sharding.key-generators.snowflake.props.max-tolerate-time-difference-milliseconds}")
private long milliseconds;
private static final Map<String, Object> CONFIG_MAP = new ConcurrentHashMap<>(2);
// ShardingSphere雪花算法 初始化时间
private static final String TIME="20161101";
private static final String FORMAT="yyyyMMdd";
private static SnowflakeKeyGenerateAlgorithm getInstance() {
return Singleton.get(SnowflakeKeyGenerateAlgorithm.class);
}
/**
* 生成一个密钥。
* 该方法是静态方法,直接通过类名调用,无需实例化对象。
* 它通过调用某个实例方法来生成并返回一个密钥。
*
* @return 返回生成的密钥,类型为long。
*/
public static long generateKey() {
return getInstance().generateKey();
}
/**
* 使用HuTool工具库生成一个唯一的长整型键值。
*
* @return 生成的唯一键值,为Snowflake算法生成的长整型ID。
*/
public static long generateKeyByHuTool() {
return getSnowflake().nextId();
}
private static Snowflake getSnowflake() {
return Singleton.get(Snowflake.class);
}
public static String getDateByHuto(long id) {
long time =getSnowflake().getGenerateDateTime(id);
DateTime date = DateUtil.date(time);
return DateUtil.format(date, FORMAT);
}
/**
* 根据给定的ID获取对应格式的日期字符串。
* 方法首先会从CONFIG_MAP中尝试获取“time”键对应的时间戳,如果未找到,则初始化时间戳,
* 并将其存储到CONFIG_MAP中。然后,根据给定的ID计算日期,并将其格式化为字符串返回。
*
* @param id 一个长整型数字,用于计算日期。
* @return 返回一个格式化的日期字符串。
*/
public static String getDate(long id) {
Long epochMilli = (Long) CONFIG_MAP.get("time");
if (Objects.isNull(epochMilli)) {
// 初始化时间
LocalDate localDate = LocalDateTimeUtil.parseDate(TIME, FORMAT);
epochMilli = LocalDateTimeUtil.toEpochMilli(localDate);
CONFIG_MAP.put("time",epochMilli);
}
long time = (id >> 22L) +epochMilli;
DateTime dateTime = DateUtil.date(time);
return DateUtil.format(dateTime, FORMAT);
}
// 实现InitializingBean接口的afterPropertiesSet方法,用于在bean初始化后执行逻辑
@Override
public void afterPropertiesSet() {
try {
SnowflakeKeyGenerateAlgorithm algorithm = new SnowflakeKeyGenerateAlgorithm();
Singleton.put(algorithm);
LocalDate localDate = LocalDateTimeUtil.parseDate(TIME, FORMAT);
long epochMilli = LocalDateTimeUtil.toEpochMilli(localDate);
CONFIG_MAP.putIfAbsent("time", epochMilli);
log.info("加载完成,map的值:{}", JSONUtil.toJsonStr(CONFIG_MAP));
Date dateTime = DateUtil.parse("20230101", FORMAT);
// hutool雪花算法 工作机器ID(workerId),不能大于20
// hutool雪花算法 数据中心ID(datacenterId),不能大于31
Snowflake snowflake = new Snowflake(dateTime, workerId, datacenterId, true, milliseconds);
Singleton.put(snowflake);
} catch (Exception e) {
log.error("加载过程中发生异常:", e);
}
}
}