一、雪花算法的实现原理
雪花算法是一个全局唯一算法,它主要出现在像分库分表场景中作为业务主键、 或者作为一些像订单号这类的 id 生成器。
所以单纯就全局唯一性质来说,有很多的实现方式,比如 UUID ,
Redis 的原子递增 ,数据库全局表的自增 id ,等等。
但是在实际应用中,还需要满足有序递增、高性能、带时间戳等。
雪花算法作为一种生成分布式全局唯一 ID 的算法,由一个 64 位的 long 类型数字组成,分为四个部分。
①、第一部分,用 1 个 bit 表示符号位,一般情况下是 0(因为第一个 bit 位是符号位,因为 id 不会是负数,所以它一般是 0)
②、第二部分,接着用 41 个 bit 位来表示毫秒单位的时间戳,使用系统时间的毫秒数
③、第三部分,用 10 个 bit 来记录工作机器 id,这样就可以保证在多个服务器上生成的 id 的唯一性。
(
如果存在跨机房部署,我们还可以把它分成两个 5bit,前面 5 个 bit 可以表示机房 id,后面 5 个 bit 可以表示机器 id。)
④、第四个部分,用 12 个 bit 表示序列号,表示一个递增序列,用来记录同毫秒内产生的不同 id
把这 64 个 bit 位拼接成一个 long 类型的数字,就是雪花算法的实现。
二、使用过程中精度丢失的问题
MyBatis-Plus在实现增删改查时,会默认将id作为主键列,并在插入数据时,默认基于雪花算法的策略生成id 。但是在使用过程中却不注意就会遇到精度丢失的问题。
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
#其他字段省略
);
@Data
public class User {
private Long id;
//其他成员变量省略
使用Long 类型对应数据库ID数据。雪花算法生成的就是一串数字,Long类型属于标准答案!在后端下断点。看到数据响应以JSON响应给前端,
{
id:1297873308628307970,
//其他属性省略
}
最后,这条数据返回给前端,前端接收到之后,修改这条数据,后端再次接收回来。
后端重新接收回来的id变成了:12978733086283000000,不再是1297873308628307970
解决办法:
①:id字段由Long类型改成String类型。(不推荐,String做ID查询性能会下降)
②: 前端用String类型雪花ID保持精度,后端数据库继续使用Long(BigINT)类型 (可以 !)
后端的ID(Long) ==> Jackson(Long转String) ==> 前端使用String类型的ID,前端使用js string精度就不会丢失了
@Configuration
public class JacksonConfig {
@Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder)
{
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// 全局配置序列化返回 JSON 处理
SimpleModule simpleModule = new SimpleModule();
//JSON Long ==> String
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}
③:放弃使用雪花算法,改用MySQL自带的生成的唯一自增id。(也行,自己选择吧)
当使用@TableId(value = "id",type = IdType.AUTO)语句时,代表着使用数据库的自增策略,注意,该类型请确保数据库设置了id自增,否则无效。
当实体类类型的类名和要操作的表的表名不一致时,就会报错,而注解@TableName就可以帮助我们解决这个问题。我的数据库表名是t_user,实体类名是User,只需要在类名上写入@TableName("t_user")就可以了
//设置实体类对应的表名
@TableName("t_user")
public class User {
@TableId(value = "id",type = IdType.AUTO)
private Long id;
当使用@TableId(value = "id")语句时,若实体类和表中表示主键的不是id,而是其他字段,例如代码中的uid,MyBatis-Plus会自动识别uid为主键列,否则就会报这样的错误: