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 // js9223372036854775807 // 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

    @JacksonStdImplpublic 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@PrimaryObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {SimpleSerializers sers = new SimpleSerializers();sers.addSerializer(Long.class, ToStringSerializer.instance); // 包装类型:Longsers.addSerializer(Long.TYPE, ToStringSerializer.instance); // 基本类型:longObjectMapper 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@PrimaryObjectMapper 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进行定制,这种方式更加简洁明了。

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机考研408真的很难吗?

408难&#xff01;科软有人四战没上岸&#xff0c;就是408拖的后腿&#xff01; 这位同学数二144英二81&#xff0c;真的太可惜了&#xff01; 是因为择校的问题吗&#xff1f; 看了他的备战经历后&#xff0c;我们发现&#xff0c;还真不是择校问题&#xff01; 是典型的备…

Jmeter03:直连数据库

1 Jmete组件&#xff1a;直连数据库 1.1 是什么&#xff1f; 让Jmeter直接和数据库交互 1.2 为什么&#xff1f; 之前是通过接口操作数据库&#xff0c;可能出现的问题&#xff1a;比如查询可能有漏查误查的情况&#xff0c;解决方案是人工对不&#xff0c;效率低且有安全隐患…

【笔记】ASP.NET Core 2.2 Web API —— 学习笔记

当年刚接触 ASP.NET Core 2.2 时&#xff0c;留下的学习笔记。现在把它挪到 CSDN&#xff0c;也是对过去学习 ASP.NET Core 痕迹进行记录。 VS 2019 ASP.NET Core 2.2 sqlSugarCore (ORM) 1. 仓储模式 服务 抽象接口 1.1 新建asp.net core 2.2 WebApi项目 nmmking.Core.…

还有同学开题报告没写吗?

引言 作为一名在软件技术领域深耕多年的专业人士&#xff0c;我不仅在软件开发和项目部署方面积累了丰富的实践经验&#xff0c;更以卓越的技术实力获得了&#x1f3c5;30项软件著作权证书的殊荣。这些成就不仅是对我的技术专长的肯定&#xff0c;也是对我的创新精神和专业承诺…

微信小程序全局配置

全局配置文件及常用的配置项 小程序根目录下的 app.json 文件是小程序的全局配置文件。常用的配置项如下&#xff1a; ① pages 记录当前小程序所有页面的存放路径 ② window 全局设置小程序窗口的外观 ③ tabBar 设置小程序底部的 tabBar 效果 ④ style 是否启用新版的组件样…

uniapp--登录和注册页面-- login

目录 1.效果展示 2.源代码展示 测试登录 login.js 测试请求 request.js 测试首页index.js 1.效果展示 2.源代码展示 <template><view><f-navbar title"登录" navbarType"4"></f-navbar><view class"tips"><…

初识--Linux的虚拟地址空间

重新了解地址空间 在学习c/c语言的时候,大家一定见过以下这张图 说的是程序会加载在如图的结构上,实际上,我们真的对他很了解吗,而在Linux进程控制这,就会有一个奇怪的现象 前提提要:简要介绍一下fork函数 进程内核数据结构(PCB)自己的代码以及数据 在Linux中,fork可以从当…

Sy-linux下常用的网络命令linux network commands

linux下的网络命令非常强大&#xff0c;这里根据教材需要&#xff0c;列出来常用的网络命令和场景实例&#xff0c;供参考。 一、命令列表&#xff1a; Command Description ip Manipulating routing to assigning and configuring network parameters traceroute Identi…

RabbitMQ Stream插件使用详解

2.4版为RabbitMQ流插件引入了对RabbitMQStream插件Java客户端的初始支持。 RabbitStreamTemplateStreamListener容器 将spring rabbit流依赖项添加到项目中&#xff1a; <dependency><groupId>org.springframework.amqp</groupId><artifactId>sprin…

Java工程师常见面试题:Java基础(一)

1、JDK 和 JRE 有什么区别&#xff1f; JDK是Java开发工具包&#xff0c;它包含了JRE和开发工具&#xff08;如javac编译器和java程序运行工具等&#xff09;&#xff0c;主要用于Java程序的开发。而JRE是Java运行环境&#xff0c;它只包含了运行Java程序所必须的环境&#xf…

Java springboot使用EasyExcel读Excel文件,映射不到属性值,对象属性值都是null

如果你的类上有这个注解&#xff0c;去掉火或注释掉就可以了 Accessors(chain true)解决方法

vue 常用的日历排班,带农历显示组件(2024-04-16)

显示当前月日历组件&#xff0c;里面带农历或节日显示 后面可以丰富一些国家法定节假期的业务需求 代码 js-calendar.js 文件 var lunarInfo [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, //1900-19090x04ae0, 0x0a5b6, 0…