Long类型的数据在网络传输的过程中丢失精度
文章目录
- 1. 准备工作
- 1.1 导入测试工程
- 1.2 运行SQL脚本
- 1.3 修改与连接数据库相关的信息
- 2. 问题呈现
- 3. 问题产生的原因
- 4. 解决方法(利用JSON序列化和反序列化)
- 4.1 特殊标识(自定义注解)
- 4.2 关键类
- 4.3 在id字段上添加自定义注解
- 4.4 成功解决
- 5. 添加JSON序列化和反序列化后与Date类型相关的注意事项
视频教程:黑马程序员Java项目实战微服务项目《黑马头条》开发全套视频教程,基于SpringBoot+SpringCloud+Nacos等企业级微服务架构项目解决方案
1. 准备工作
1.1 导入测试工程
为了复现问题,我们先导入测试工程,测试工程的 Gitee 地址:long-type-precision-loss-problem
1.2 运行SQL脚本
SQL 脚本在测试工程的 sql 目录下
1.3 修改与连接数据库相关的信息
application.yml
2. 问题呈现
启动测试工程后,在浏览器访问项目
http://localhost:12100/
我们拿第一条文章的 ID 到数据库中进行查询,发现没有结果
select *
from ap_article
where id = 1383827787629252600;
这是为什么呢,明明文章的 id 是后端返回的,为什么用后端返回的文章 id 却查不到文章的信息呢
3. 问题产生的原因
我们换成根据文章标题查询,却发现能查询出对应的文章
select *
from ap_article
where title = 'Kafka文件的存储机制';
这是为什么呢
我们对比一下数据库返回的文章 id 和前端显示的文章 id,可以发现两个 id 的末尾部分不一致(白底的是前端显示的,黑底的是数据库返回的)
而且 id 列的每一个数据末尾部分都是 00
问题产生的原因就是 Long 类型的数据在进行网络传输的时候,精度丢失了
4. 解决方法(利用JSON序列化和反序列化)
我们可以利用 JSON 序列化和反序列化来解决 Long类型的数据在网络传输的过程中丢失精度的问题
以下是整体思路
- 当后端响应给前端的数据中包含了 id 或者特殊标识(自定义)的时候,把当前数据进行转换为 String 类型
- 当前端传递给后端的 dto 中有 id 或者特殊标识(自定义)的时候,把当前数据转为 Integer 或 Long 类型
4.1 特殊标识(自定义注解)
那如何标记某个字段是否需要进行序列化和反序列化呢
我们可以通过自定义注解实现,如果某个字段需要进行序列化和反序列化,就为该字段添加自定义注解
import com.fasterxml.jackson.annotation.JacksonAnnotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@JacksonAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface IdEncrypt {
}
4.2 关键类
关键的几个类放在了 jackson 包下
几个类的功能如下:
类名 | 功能描述 |
---|---|
ConfusionSerializer | 自定义序列化器,用于序列化时处理自增数字的混淆,将非空值转换为字符串并写入JSON |
ConfusionDeserializer | 自定义反序列化器,用于反序列化时处理自增数字的混淆解密,根据字段类型进行相应的解析 |
ConfusionSerializerModifier | 序列化修改器,用于过滤和修改序列化过程中处理的字段,为需要混淆的字段分配自定义序列化器 |
ConfusionDeserializerModifier | 反序列化修改器,用于过滤和修改反序列化过程中处理的字段,为需要解密的字段分配自定义反序列化器 |
ConfusionModule | Jackson模块,用于注册自定义的序列化和反序列化修改器,配置ObjectMapper的全局特性 |
InitJacksonConfig | Spring配置类,提供自动化的ObjectMapper配置,注册ConfusionModule,使框架自动处理ID混淆 |
只要某个字段上有自定义注解或者字段的名字为 id,就会自动完成序列化和反序列化的操作
import cn.edu.scau.enums.IdEncrypt;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import java.util.Iterator;
public class ConfusionDeserializerModifier extends BeanDeserializerModifier {
@Override
public BeanDeserializerBuilder updateBuilder(final DeserializationConfig config, final BeanDescription beanDescription, final BeanDeserializerBuilder builder) {
Iterator<SettableBeanProperty> it = builder.getProperties();
while (it.hasNext()) {
SettableBeanProperty settableBeanProperty = it.next();
if ((null != settableBeanProperty.getAnnotation(IdEncrypt.class) || settableBeanProperty.getName().equalsIgnoreCase("id"))) {
builder.addOrReplaceProperty(settableBeanProperty.withValueDeserializer(new ConfusionDeserializer(settableBeanProperty.getValueDeserializer(), settableBeanProperty.getType())), true);
}
}
return builder;
}
}
4.3 在id字段上添加自定义注解
@IdEncrypt
4.4 成功解决
完成上述配置后,重启项目,可以看到前端显示 id 列时已经没有精度丢失的问题了
我们随机挑选一个 id,到数据库中进行查询,发现也能查询到对应的文章
select *
from ap_article
where id = 1383827787629252610;
5. 添加JSON序列化和反序列化后与Date类型相关的注意事项
添加了 JSON 序列化和反序列化之后,Date 类型的数据返回给前端时会转换成时间戳
添加 JSON 序列化和反序列化之前
添加 JSON 序列化和反序列化之后
如果不想让 Date 类型的数据返回给前端时转换成时间戳,可以忽略对 Date 类型的转换
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);