springboot?Long?精度丟失問(wèn)題解決
前言
最近在開(kāi)發(fā)中,碰到一個(gè)問(wèn)題,關(guān)于數(shù)據(jù)庫(kù)Long類(lèi)型查詢(xún)后,返回給前端后,精度丟失。
297874820157145088 => 297874820157145100
數(shù)據(jù)庫(kù)數(shù)據(jù)如下:
id | 字典碼 | 字典名稱(chēng) | 備注 |
---|---|---|---|
297874820157145088 | enableFlag | 啟用狀態(tài) | 0未啟用,1啟用 |
查詢(xún)語(yǔ)句如下:
SELECT id AS id, dict_code AS dictCode, dict_name AS dictName, remark AS remark, create_time AS createTime, create_by AS createBy, update_time AS updateTime, update_by AS updateBy, del_flag AS delFlag FROM sys_dict WHERE (del_flag=0) LIMIT 10 OFFSET 0
查詢(xún)結(jié)果:(正確,此時(shí)并未出現(xiàn)精度丟失)
id | 字典碼 | 字典名稱(chēng) | 備注 |
---|---|---|---|
297874820157145088 | enableFlag | 啟用狀態(tài) | 0未啟用,1啟用 |
再來(lái)看一下Springboot準(zhǔn)備返回時(shí)的數(shù)據(jù)顯示:
這里可以看到返回類(lèi)型是Long類(lèi)型且精度并未丟失。
可以看到這時(shí)前端顯示出來(lái)的,就出現(xiàn)問(wèn)題了。是不是網(wǎng)絡(luò)傳輸?shù)倪^(guò)程有問(wèn)題呢?我們換PostMan試一下。
可以看見(jiàn)這里的id是正確的,那么應(yīng)該時(shí)JavaScript導(dǎo)致的精度丟失了。
大致看了一下網(wǎng)上的思路,因?yàn)镴avaScript 的整數(shù)類(lèi)型范圍有限,精度為17位 ,當(dāng)接口返回的Long類(lèi)型過(guò)長(zhǎng)時(shí),javaScript會(huì)進(jìn)行截?cái)?,所以解決辦法就是把Long類(lèi)型轉(zhuǎn)換成String類(lèi)型返回,這樣就不會(huì)由精度丟失的問(wèn)題了。
解決方法
基于注解@JsonSerialize(不推薦)
最直接的方式就是在需要轉(zhuǎn)換的Long類(lèi)型字段使用注解標(biāo)記。
@JsonSerialize private Long id;
這樣子好處是可以對(duì)單個(gè)的字段進(jìn)行精細(xì)管理,但是需要每個(gè)字段都添加轉(zhuǎn)換,很不方便,工作量大。
基于jackson全局配置(不推薦)
spring: jackson: generator: write-numbers-as-strings: true
這個(gè)方法會(huì)將全局所有的數(shù)字類(lèi)型包括int/long等都轉(zhuǎn)換為string類(lèi)型返回,不推薦使用。
使用JsonComponent 序列化配置
@JsonComponent public class JsonConfig { /** * 添加Long轉(zhuǎn)json精度丟失的配置 */ @Bean public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); SimpleModule module = new SimpleModule(); module.addSerializer(Long.class, ToStringSerializer.instance); module.addSerializer(Long.TYPE, ToStringSerializer.instance); objectMapper.registerModule(module); return objectMapper; } }
為什么這樣寫(xiě)能生效呢?我們可以看一下 WebMvcConfigurationSupport 類(lèi)。
WebMvcConfigurationSupport 分析
//初始化 static { // etc... jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); } // 添加默認(rèn)的httpMesssage轉(zhuǎn)換器 protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) { //etc if (jackson2Present) { builder = Jackson2ObjectMapperBuilder.json(); if (this.applicationContext != null) { builder.applicationContext(this.applicationContext); } messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build())); } }
這里可以看出來(lái)Springboot提供的默認(rèn)mvc配置內(nèi)容:
- 初始化并查找是否有 ObjectMapper 類(lèi)
- 如果沒(méi)有發(fā)現(xiàn) ObjectMapper Bean對(duì)象,就會(huì)提供給一個(gè)默認(rèn)的 MappingJackson2HttpMessageConverter 對(duì)象。
- 如果發(fā)現(xiàn) ObjectMapper Bean對(duì)象,就會(huì)將這個(gè)綁定到默認(rèn)的轉(zhuǎn)換器上。
WebMvcConfigurer/WebMvcConfigurationSupport
這里雖然將這兩個(gè)類(lèi)放一起,是因?yàn)槎寄芑谒麄內(nèi)ブ貙?xiě) configureMessageConverters方法,來(lái)實(shí)現(xiàn)對(duì)轉(zhuǎn)換器的添加。
@Configuration public class WebConfig implements WebMvcConfigurer { /** * 添加Long轉(zhuǎn)json精度丟失的配置 * * @Return: void */ @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); ObjectMapper objectMapper = new ObjectMapper(); SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); objectMapper.registerModule(simpleModule); jackson2HttpMessageConverter.setObjectMapper(objectMapper); converters.add(jackson2HttpMessageConverter); } }
這里需要注意的是,該方法可能存在失效的情況,但是如果我們改成這樣,就能有效。
converters.add(0,jackson2HttpMessageConverter);
這是為什么呢?讓我們探究一下。
分析
先看一下都有什么轉(zhuǎn)換器。
可以看到在我們添加轉(zhuǎn)換器前,就已經(jīng)有了2個(gè) Mappingjackson2HttpMessageConverter 了。我們說(shuō)過(guò)springboot 自帶了 HttpMessageConverter 。
那么這時(shí)出現(xiàn)多個(gè),會(huì)不會(huì)相互之間有影響?
// AbstractMessageConverterMethodProcessor.class if (selectedMediaType != null) { selectedMediaType = selectedMediaType.removeQualityValue(); for (HttpMessageConverter<?> converter : this.messageConverters) { GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null); if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : converter.canWrite(valueType, selectedMediaType)) { body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) converter.getClass(), inputMessage, outputMessage); if (body != null) { Object theBody = body; LogFormatUtils.traceDebug(logger, traceOn -> "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]"); addContentDispositionHeader(inputMessage, outputMessage); if (genericConverter != null) { genericConverter.write(body, targetType, selectedMediaType, outputMessage); } else { ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage); } } else { if (logger.isDebugEnabled()) { logger.debug("Nothing to write: null body"); } } return; } } } // AbstractMessageConverterMethodArgumentResolver.class for (HttpMessageConverter<?> converter : this.messageConverters) { Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass(); GenericHttpMessageConverter<?> genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null); if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) : (targetClass != null && converter.canRead(targetClass, contentType))) { if (message.hasBody()) { HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType); body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) : ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse)); body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType); } else { body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType); } break; } }
這里可以看到,會(huì)遍歷messageConverters轉(zhuǎn)換器列表,但是問(wèn)題在于,如果有一個(gè) HttpMessageConverter 類(lèi)響應(yīng)了讀寫(xiě)信息,那么就會(huì)進(jìn)行返回,這樣導(dǎo)致了后面的轉(zhuǎn)換器不生效。
解決方法
那么針對(duì)這種有幾種解決方法
- 提升自定義的消息處理轉(zhuǎn)換器優(yōu)先級(jí);
converters.add(0,jackson2HttpMessageConverter);
- 移除列表里的springboot 默認(rèn)的轉(zhuǎn)換器;
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
- 使用@EnableWebMvc 注解。慎用,會(huì)清空sprinb自帶的默認(rèn)轉(zhuǎn)換器導(dǎo)致某些功能失效。此時(shí)的converters轉(zhuǎn)換列表為空。
@EnableWebMvc @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); ObjectMapper objectMapper = new ObjectMapper(); SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); objectMapper.registerModule(simpleModule); jackson2HttpMessageConverter.setObjectMapper(objectMapper); converters.add(jackson2HttpMessageConverter); } }
以上就是springboot Long 精度丟失問(wèn)題解決的詳細(xì)內(nèi)容,更多關(guān)于springboot Long 精度丟失的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot集成P6Spy實(shí)現(xiàn)SQL日志的記錄詳解
P6Spy是一個(gè)框架,它可以無(wú)縫地?cái)r截和記錄數(shù)據(jù)庫(kù)活動(dòng),而無(wú)需更改現(xiàn)有應(yīng)用程序的代碼。一般我們使用的比較多的是使用p6spy打印我們最后執(zhí)行的sql語(yǔ)句2022-11-11查看Java所支持的語(yǔ)言及相應(yīng)的版本信息
Java語(yǔ)言作為第一種支持國(guó)際化的語(yǔ)言,在Internet從一開(kāi)始就具有其他語(yǔ)言無(wú)與倫比的國(guó)際化的本質(zhì)特性,查看Java所支持的語(yǔ)言及相應(yīng)的版本信息可以采用以下代碼進(jìn)行查詢(xún)2014-01-01Java Spring使用hutool的HttpRequest發(fā)送請(qǐng)求的幾種方式
文章介紹了Hutool庫(kù)中用于發(fā)送HTTP請(qǐng)求的工具,包括添加依賴(lài)、發(fā)送GET和POST請(qǐng)求的方法,以及GET請(qǐng)求的不同參數(shù)傳遞方式,感興趣的朋友跟隨小編一起看看吧2024-11-11SpringBoot后臺(tái)實(shí)現(xiàn)文件上傳下載
這篇文章主要為大家詳細(xì)介紹了SpringBoot后臺(tái)實(shí)現(xiàn)文件上傳下載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02java面試題——詳解HashMap和Hashtable 的區(qū)別
本篇文章主要介紹了java中HashMap和Hashtable的區(qū)別,具有一定的參考價(jià)值,有需要的可以了解一下。2016-11-11idea啟動(dòng)tomcat控制臺(tái)中文亂碼的三種情況解決
本文主要介紹了idea啟動(dòng)tomcat控制臺(tái)中文亂碼,主要包括三種情況,分別介紹了一下解決方法,具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10Java實(shí)現(xiàn)多文件壓縮加密并重命名壓縮文件對(duì)象的方法
這篇文章主要介紹了Java實(shí)現(xiàn)多文件壓縮加密并重命名壓縮文件對(duì)象的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01JDK12的新特性之CompactNumberFormat詳解
這篇文章主要介紹了JDK12的新特性之CompactNumberFormat,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05java實(shí)現(xiàn)將字符串中首字母轉(zhuǎn)換成大寫(xiě),其它全部轉(zhuǎn)換成小寫(xiě)的方法示例
這篇文章主要介紹了java實(shí)現(xiàn)將字符串中首字母轉(zhuǎn)換成大寫(xiě),其它全部轉(zhuǎn)換成小寫(xiě)的方法,涉及java字符串遍歷、轉(zhuǎn)換、拼接等相關(guān)操作技巧,需要的朋友可以參考下2019-06-06