Jackson 2.x 系列【28】Spring Boot 集成之 Long 精度损失

有道无术,术尚可求,有术无道,止于术。

本系列Jackson 版本 2.17.0

本系列Spring Boot 版本 3.2.4

源码地址:https://gitee.com/pearl-organization/study-jaskson-demo

文章目录

    • 1. 问题场景
    • 2. 原因分析
    • 3. 解决方案
    • 4. 案例演示
      • 4.1 方式一:使用注解
      • 4.2 方式二:全局配置
        • 4.2.1 方案分析
        • 4.2.2 配置

1. 问题场景

当前用户表主键ID采用雪花算法生成,实体类对应的类型为Long

    @ApiModelProperty(value = "主键ID")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;

返回前端时,发现Long精度丢失,例如ID362909601374617692时,前端拿到的值却是362909601374617660,导致ID错误查询不到当前数据,甚至可能查询到了错误的数据。

2. 原因分析

Java中,long是一种基本数据类型,用于表示整数,它属于8字节(64位)的有符号类型,最小值为-263次方,即-9223372036854775808,最大值为263次方-1,即9223372036854775807

JavaScript中,Number类型是基本数据类型之一,用于表示数字,可以包含整数、浮点数、负数等各种数值类型。整数表示时的最小值为-253次方-1,即-9007199254740991,最大值为253次方-1,即9007199254740991

Java直接返回Long整型数据给前端时,JS会自动转换为Number类型,从下面的对比中,可以很直观的看到,当超过JS的整数范围时,该数值会精度损失。

-9223372036854775808 // long
-9007199254740991 // js

9223372036854775807 // long
9007199254740991 // js

3. 解决方案

对于需要使用超大整数的场景,服务端一律使用 String 字符串类型返回,禁止使用Long类型。

4. 案例演示

测试接口如下:

@RestController
public class UserController {

    @RequestMapping("/test")
    public UserVO test() {
        UserVO userVO = new UserVO();
        userVO.setId(1699657986705854464L);
        userVO.setUsername("jack");
        userVO.setBirthday(new Date());
        List<String> roleList = new ArrayList<>();
        roleList.add("管理员");
        roleList.add("经理");
        userVO.setRoleList(roleList);
        return userVO;
    }
}

4.1 方式一:使用注解

针对Long类型的属性,使用@JsonSerialize注解指定序列化器为ToStringSerializer

    @JsonSerialize(using = ToStringSerializer.class)
    Long id;

输出结果:

{
  "id": "1699657986705854464",
  "username": "jack",
  "roleList": "管理员,经理",
  "birthday": "2024-04-16 14:40:39"
}

4.2 方式二:全局配置

上面的配置只能作用于被注解标识的字段,实际的需求需要作用有所有Long类型,所以需要全局配置。

4.2.1 方案分析

首先默认情况下,Long类型的数据是调用的LongSerializer 进行序列化的,需要指定Long类型对应的序列化器为ToStringSerializer

    @JacksonStdImpl
    public static class LongSerializer extends Base<Object> {
        public LongSerializer(Class<?> cls) {
            super(cls, NumberType.LONG, "integer");
        }

        public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            gen.writeNumber((Long)value);
        }
    }

在工厂 BeanSerializerFactory创建序列化器的_createSerializer2方法中,可以看到会优先查询自定义序列化器:

    protected JsonSerializer<?> _createSerializer2(SerializerProvider prov,
                                                   JavaType type, BeanDescription beanDesc, boolean staticTyping)
            throws JsonMappingException {
        JsonSerializer<?> ser = null;
        final SerializationConfig config = prov.getConfig();

        // 容器类型 (LIST、MAP)
        if (type.isContainerType()) {
            if (!staticTyping) {
                staticTyping = usesStaticTyping(config, beanDesc);
            }
            // 03-Aug-2012, tatu: As per [databind#40], may require POJO serializer...
            ser = buildContainerSerializer(prov, type, beanDesc, staticTyping);
            // Will return right away, since called method does post-processing:
            if (ser != null) {
                return ser;
            }
        } else {
            // 非容器类型
            if (type.isReferenceType()) {
                ser = findReferenceSerializer(prov, (ReferenceType) type, beanDesc, staticTyping);
            } else {
                // POJO类型
                // 查询模块中定义的序列化器(通过SerializerFactoryConfig 配置)
                for (Serializers serializers : customSerializers()) {
                    ser = serializers.findSerializer(config, type, beanDesc);
                    if (ser != null) {
                        break;
                    }
                }
            }
            // 查询注解中定义的序列化类型
            if (ser == null) {
                ser = findSerializerByAnnotations(prov, type, beanDesc);
            }
        }

这里的自定义序列化器是指通过SerializerFactory API直接添加,或者通过模块注册的序列化器:


public abstract class SerializerFactory {

    public abstract SerializerFactory withAdditionalSerializers(Serializers sers);
    // 省略.....
}

// 注册模块方法中,实际也是调用了 SerializerFactory 
public void addDeserializers(Deserializers d) {
         DeserializerFactory df = ObjectMapper.this._deserializationContext._factory.withAdditionalDeserializers(d);
         ObjectMapper.this._deserializationContext = ObjectMapper.this._deserializationContext.with(df);
}

例如,对jsr310支持的模块JavaTimeModule中,时间类型对应的序列化器都添加进来了:

在这里插入图片描述
总结:我们只需要通过SerializerFactory API或者通过模块注册,指定Long类型对应的序列化器为ToStringSerializer

4.2.2 配置

1、SerializerFactory

直接在注册ObjectMapper 时,通过SerializerFactory配置:

    @Bean
    @Primary
    ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {

        SimpleSerializers sers = new SimpleSerializers();
        sers.addSerializer(Long.class, ToStringSerializer.instance); // 包装类型:Long
        sers.addSerializer(Long.TYPE, ToStringSerializer.instance); // 基本类型:long

        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        SerializerFactory serializerFactory = objectMapper
                .getSerializerFactory()
                .withAdditionalSerializers(sers)
                .withSerializerModifier(new NullValueBeanSerializerModifier());
        objectMapper.setSerializerFactory(serializerFactory);
        return objectMapper;
    }

测试结果如下:

{
  "id": "1699657986705854464",
  "username": "jack",
  "roleList": "管理员,经理",
  "birthday": "2024-04-16 16:03:22"
}

2、Jackson2ObjectMapperBuilder

Jackson2ObjectMapperBuilder中声明了添加序列化器的相关方法:

    public Jackson2ObjectMapperBuilder serializerByType(Class<?> type, JsonSerializer<?> serializer) {
        this.serializers.put(type, serializer);
        return this;
    }

    public Jackson2ObjectMapperBuilder serializersByType(Map<Class<?>, JsonSerializer<?>> serializers) {
        this.serializers.putAll(serializers);
        return this;
    }

这些添加的序列化器的会在configure方法中,通过模块注册到ObjectMapper中:

        if (!this.serializers.isEmpty() || !this.deserializers.isEmpty()) {
            SimpleModule module = new SimpleModule();
            this.addSerializers(module);
            this.addDeserializers(module);
            objectMapper.registerModule(module);
        }

使用Jackson2ObjectMapperBuilder构建对象时配置ToStringSerializer

    @Bean
    @Primary
    ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder
                .serializerByType(Long.class, ToStringSerializer.instance)
                .serializerByType(Long.TYPE, ToStringSerializer.instance)
                .createXmlMapper(false).build();
        SerializerFactory serializerFactory = objectMapper
                .getSerializerFactory()
                .withSerializerModifier(new NullValueBeanSerializerModifier());
        objectMapper.setSerializerFactory(serializerFactory);
        return objectMapper;
    }

3、Jackson2ObjectMapperBuilderCustomizer

使用Jackson2ObjectMapperBuilderCustomizer进行定制,这种方式更加简洁明了。

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            builder.serializerByType(Long.class, ToStringSerializer.instance);
            builder.serializerByType(Long.TYPE, ToStringSerializer.instance);
        };
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/550841.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python 物联网入门指南(七)

原文&#xff1a;zh.annas-archive.org/md5/4fe4273add75ed738e70f3d05e428b06 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第二十四章&#xff1a;基本开关 到目前为止一定是一段史诗般的旅程&#xff01;回想一下你开始阅读这本书的时候&#xff0c;你是否曾想象…

v-for中涉及的key

一、为什么要用key&#xff1f; key可以标识列表中每个元素的唯一性&#xff0c;方便Vue高效地更新虚拟DOM&#xff1b;key主要用于dom diff算法&#xff0c;diff算法是同级比较&#xff0c;比较当前标签上的key和标签名&#xff0c;如果都一样&#xff0c;就只移动元素&#…

(十二)C++自制植物大战僵尸游戏多用户存档实现(一)

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/8UFMs 游戏存档 游戏存档允许玩家保存游戏进度&#xff0c;以便在之后的时间继续游戏。通过存档&#xff0c;玩家可以暂停游戏并在需要时重新开始&#xff0c;而不必从头开始或重新完成已经完成的任务。游戏通常提供多个…

VAR:自回归家族文生图新SOTA,ImageNet上超越Diffusion与DiTs

一、背景&#xff1a; 在人工智能领域&#xff0c;尤其是计算机视觉和自然语言处理中&#xff0c;自回归&#xff08;AR&#xff09;大型模型&#xff08;如GPT系列&#xff09;因其强大的生成能力和在多种任务上的通用性而受到广泛关注。这些模型通过自监督学习策略&#xff0…

PMP有用吗,PMP含金量,如何转型项目经理?

为什么要学习PMP知识&#xff0c;PMP培训哪家好&#xff1f; IT行业项目管理一枚&#xff0c;曾在做技术的时候对自己的职业发展越来越迷茫&#xff0c;不想干到35岁就参与到失业潮中&#xff0c;一直在想着办法提升自己的能力和竞争力&#xff0c;直到了解到了PMP认证。也就是…

二维码门楼牌管理应用平台建设:场所维护的新篇章

文章目录 前言一、二维码门楼牌管理应用平台的兴起二、民警与网格员的角色定位三、场所信息审核的重要性四、技术支持与创新应用五、未来展望与挑战 前言 随着信息技术的飞速发展&#xff0c;二维码门楼牌管理应用平台的建设正成为城市管理的新宠。该平台不仅提高了场所管理的…

HR招聘人才测评,如何考察候选人的内驱力?

HR的日常招聘工作中&#xff0c;如何去评估候选人的内驱力。人的内驱力&#xff0c;在职业生涯中&#xff0c;是极为重要的品质&#xff0c;也被列入综合素质测评。 内驱力&#xff0c;是指一个人出于内心深处的热情和追求&#xff0c;自发驱动自己持续学习、不断进步&#xf…

jenkins从节点配置说明

目的 打包构建时使用从节点&#xff0c;从节点所在服务器配置4C8G5000G&#xff08;服务器2&#xff09; 前提 首先在服务器1上部署jenkins服务&#xff0c;即主节点&#xff0c;默认节点名称为master 步骤 1&#xff09;登录进入jenkins平台&#xff0c;在系统设置中&…

项目风采展示【车酷-保时捷第二屏】

桌面功能介绍&#xff1a; 1&#xff1a;支持本地app桌面展示 2&#xff1a;支持本地音乐控制

LeetCode 每日一题 Day 123-136

1379. 找出克隆二叉树中的相同节点 给你两棵二叉树&#xff0c;原始树 original 和克隆树 cloned&#xff0c;以及一个位于原始树 original 中的目标节点 target。 其中&#xff0c;克隆树 cloned 是原始树 original 的一个 副本 。 请找出在树 cloned 中&#xff0c;与 tar…

自学Java的第二十四次笔记

一,方法重载 1.基本介绍 java 中允许同一个类中&#xff0c;多个同名方法的存在&#xff0c;但要求 形参列表不一致&#xff01; 比如&#xff1a; System.out.println(); out 是 PrintStream 类型 2.重载的好处 1) 减轻了起名的麻烦 2) 减轻了记名的麻烦 3.快速入门案…

git 小记

一、 github新建仓库 git clone 。。。。。。。。。。。 &#xff08;增删查补&#xff0c;修改&#xff09; git add . git commit -m "修改” git push (git push main) 二、branch 分支 branch并不难理解&#xff0c;你只要想像将代码拷贝到不同目录…

Modality-Aware Contrastive Instance Learning with Self-Distillation ... 论文阅读

Modality-Aware Contrastive Instance Learning with Self-Distillation for Weakly-Supervised Audio-Visual Violence Detection 论文阅读 ABSTRACT1 INTRODUCTION2 RELATEDWORKS2.1 Weakly-Supervised Violence Detection2.2 Contrastive Learning2.3 Cross-Modality Knowle…

盲人安全导航技巧:科技赋能让出行更自如

作为一名资深记者&#xff0c;长期关注并报道无障碍领域的发展动态。今日&#xff0c;我将聚焦盲人安全导航技巧&#xff0c;探讨这一主题下科技如何赋能视障人士实现更为安全、独立的出行。一款融合了实时避障、拍照识别物体及场景功能的盲人出行辅助应用叫做蝙蝠避障&#xf…

软考 - 系统架构设计师 - Web 应用真题(2)

问题 1&#xff1a; 淘汰策略&#xff1a;遗留系统技术含量低&#xff0c;业务价值也低&#xff0c;所以需要全面重新开发一个系统来替代遗留系&#xff1b;&#xff08;一般是企业的业务发生了根本变化&#xff0c;遗留系统已经基本不再适应企业运作的需要&#xff1b;或者是遗…

C语言进阶课程学习记录-数组指针和指针数组分析

C语言进阶课程学习记录-数组指针和指针数组分析 实验-数组指针的大小实验-指针数组小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课程PPT&#xff0c;仅用于个人学习记录 实验-数组指针的大小 #include <stdio.h>typedef int(AINT…

【微信小程序之分包】

微信小程序之分包 什么是分包分包的好处分包前的结构图分包后的结构图分包的加载规则分包的体积限制使用分包打包原则引用原则独立分包独立分包的配置方法独立分包的引用原则分包预下载配置分包的预下载分包预下载限制 什么是分包 分包指的是把一个完整小程序项目&#xff0c;…

理想低通滤波器

理想低通滤波器&#xff0c;振铃现象是因为sinc函数&#xff0c;而sinc函数是因为例4.1的简单函数的傅里叶变换得到的。经过我的计算&#xff0c;简单函数的傅里叶反变换也得到sinc函数。这里的频率域滤波器因为是二个值的&#xff0c;所以类似简单函数&#xff0c;反变换之后得…

DRV8711驱动器的各寄存器的介绍

一、CTRL Register (Address = 0x00) ISENSE放大器增益设置:设定值越大时,表示在任何频率的指令脉冲下,位置滞后量越小;位置环的前馈增益大,控制系统的高速响应特性提高,但会使系统的位置不稳定,容易产生振荡; 死亡时间设置:电机驱动死区时间指的是在电机的控制信号由…

AI智能体技术突破:引领科技新浪潮

AI智能体技术突破&#xff1a;引领科技新浪潮 基于大模型的 AI Agent 工作流基于大模型的 AI Agent 工作流效果AI Agent 的四种设计模式Reflection 反思设计模式Tool use 工具使用设计模式Planning 规划设计模式Multiagent collaboration 多智能体协作设计模式 吴恩达在红杉美国…