springboot?Long?精度丟失問題解決
前言
最近在開發(fā)中,碰到一個(gè)問題,關(guān)于數(shù)據(jù)庫Long類型查詢后,返回給前端后,精度丟失。
297874820157145088 => 297874820157145100
數(shù)據(jù)庫數(shù)據(jù)如下:
| id | 字典碼 | 字典名稱 | 備注 |
|---|---|---|---|
| 297874820157145088 | enableFlag | 啟用狀態(tài) | 0未啟用,1啟用 |
查詢語句如下:
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
查詢結(jié)果:(正確,此時(shí)并未出現(xiàn)精度丟失)
| id | 字典碼 | 字典名稱 | 備注 |
|---|---|---|---|
| 297874820157145088 | enableFlag | 啟用狀態(tài) | 0未啟用,1啟用 |
再來看一下Springboot準(zhǔn)備返回時(shí)的數(shù)據(jù)顯示:

這里可以看到返回類型是Long類型且精度并未丟失。


可以看到這時(shí)前端顯示出來的,就出現(xiàn)問題了。是不是網(wǎng)絡(luò)傳輸?shù)倪^程有問題呢?我們換PostMan試一下。

可以看見這里的id是正確的,那么應(yīng)該時(shí)JavaScript導(dǎo)致的精度丟失了。
大致看了一下網(wǎng)上的思路,因?yàn)镴avaScript 的整數(shù)類型范圍有限,精度為17位 ,當(dāng)接口返回的Long類型過長時(shí),javaScript會進(jìn)行截?cái)?,所以解決辦法就是把Long類型轉(zhuǎn)換成String類型返回,這樣就不會由精度丟失的問題了。
解決方法
基于注解@JsonSerialize(不推薦)
最直接的方式就是在需要轉(zhuǎn)換的Long類型字段使用注解標(biāo)記。
@JsonSerialize private Long id;
這樣子好處是可以對單個(gè)的字段進(jìn)行精細(xì)管理,但是需要每個(gè)字段都添加轉(zhuǎn)換,很不方便,工作量大。
基于jackson全局配置(不推薦)
spring: jackson: generator: write-numbers-as-strings: true

這個(gè)方法會將全局所有的數(shù)字類型包括int/long等都轉(zhuǎn)換為string類型返回,不推薦使用。
使用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;
}
}
為什么這樣寫能生效呢?我們可以看一下 WebMvcConfigurationSupport 類。
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()));
}
}
這里可以看出來Springboot提供的默認(rèn)mvc配置內(nèi)容:
- 初始化并查找是否有 ObjectMapper 類
- 如果沒有發(fā)現(xiàn) ObjectMapper Bean對象,就會提供給一個(gè)默認(rèn)的 MappingJackson2HttpMessageConverter 對象。
- 如果發(fā)現(xiàn) ObjectMapper Bean對象,就會將這個(gè)綁定到默認(rèn)的轉(zhuǎn)換器上。
WebMvcConfigurer/WebMvcConfigurationSupport
這里雖然將這兩個(gè)類放一起,是因?yàn)槎寄芑谒麄內(nèi)ブ貙?configureMessageConverters方法,來實(shí)現(xiàn)對轉(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 了。我們說過springboot 自帶了 HttpMessageConverter 。
那么這時(shí)出現(xiàn)多個(gè),會不會相互之間有影響?
// 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;
}
}
這里可以看到,會遍歷messageConverters轉(zhuǎn)換器列表,但是問題在于,如果有一個(gè) HttpMessageConverter 類響應(yīng)了讀寫信息,那么就會進(jìn)行返回,這樣導(dǎo)致了后面的轉(zhuǎn)換器不生效。
解決方法
那么針對這種有幾種解決方法
- 提升自定義的消息處理轉(zhuǎn)換器優(yōu)先級;
converters.add(0,jackson2HttpMessageConverter);
- 移除列表里的springboot 默認(rèn)的轉(zhuǎn)換器;
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
- 使用@EnableWebMvc 注解。慎用,會清空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 精度丟失問題解決的詳細(xì)內(nèi)容,更多關(guān)于springboot Long 精度丟失的資料請關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot傳給前端Long類型精度丟失的解決方案
- SpringBoot返回long,前端接收進(jìn)度丟失,@JsonSerialize不生效問題
- SpringBoot返回前端Long類型字段丟失精度問題及解決方案
- SpringBoot分頁的實(shí)現(xiàn)與long型id精度丟失問題的解決方案介紹
- SpringBoot2.0解決Long型數(shù)據(jù)轉(zhuǎn)換成json格式時(shí)丟失精度問題
- SpringBoot全局配置long轉(zhuǎn)String丟失精度的問題解決
- SpringBoot全局配置long轉(zhuǎn)String丟失精度問題解決方案
- SpringBoot基于Jackson解決Long型長度丟失問題
相關(guān)文章
手把手教你怎么創(chuàng)建spring項(xiàng)目
今天教大家怎么寫spring項(xiàng)目,文中有非常詳細(xì)的圖文示例及介紹,對正在學(xué)習(xí)java的小伙伴們很有幫助,需要的朋友可以參考下2021-06-06
java+Okhttp3調(diào)用接口的實(shí)例
這篇文章主要介紹了java+Okhttp3調(diào)用接口的實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
HttpServletRequest對象常用功能_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了HttpServletRequest對象常用功能的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
淺談Java中ThreadLocal內(nèi)存泄露的原因及處理方式
內(nèi)存泄漏就是我們申請了內(nèi)存,但是該內(nèi)存一直無法釋放,就會導(dǎo)致內(nèi)存溢出問題,本文詳細(xì)的介紹了ThreadLocal內(nèi)存泄露的原因及處理方式,感興趣的可以了解一下2023-05-05
JetBrains IntelliJ IDEA 配置優(yōu)化技巧
這篇文章主要介紹了JetBrains IntelliJ IDEA 配置優(yōu)化技巧,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
Java實(shí)現(xiàn)支付對接常用加密方式的示例代碼

