springboot?Long?精度丟失問題解決
前言
最近在開發(fā)中,碰到一個問題,關于數據庫Long類型查詢后,返回給前端后,精度丟失。
297874820157145088 => 297874820157145100
數據庫數據如下:
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
查詢結果:(正確,此時并未出現精度丟失)
id | 字典碼 | 字典名稱 | 備注 |
---|---|---|---|
297874820157145088 | enableFlag | 啟用狀態(tài) | 0未啟用,1啟用 |
再來看一下Springboot準備返回時的數據顯示:
這里可以看到返回類型是Long類型且精度并未丟失。
可以看到這時前端顯示出來的,就出現問題了。是不是網絡傳輸的過程有問題呢?我們換PostMan試一下。
可以看見這里的id是正確的,那么應該時JavaScript導致的精度丟失了。
大致看了一下網上的思路,因為JavaScript 的整數類型范圍有限,精度為17位 ,當接口返回的Long類型過長時,javaScript會進行截斷,所以解決辦法就是把Long類型轉換成String類型返回,這樣就不會由精度丟失的問題了。
解決方法
基于注解@JsonSerialize(不推薦)
最直接的方式就是在需要轉換的Long類型字段使用注解標記。
@JsonSerialize private Long id;
這樣子好處是可以對單個的字段進行精細管理,但是需要每個字段都添加轉換,很不方便,工作量大。
基于jackson全局配置(不推薦)
spring: jackson: generator: write-numbers-as-strings: true
這個方法會將全局所有的數字類型包括int/long等都轉換為string類型返回,不推薦使用。
使用JsonComponent 序列化配置
@JsonComponent public class JsonConfig { /** * 添加Long轉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); } // 添加默認的httpMesssage轉換器 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提供的默認mvc配置內容:
- 初始化并查找是否有 ObjectMapper 類
- 如果沒有發(fā)現 ObjectMapper Bean對象,就會提供給一個默認的 MappingJackson2HttpMessageConverter 對象。
- 如果發(fā)現 ObjectMapper Bean對象,就會將這個綁定到默認的轉換器上。
WebMvcConfigurer/WebMvcConfigurationSupport
這里雖然將這兩個類放一起,是因為都能基于他們去重寫 configureMessageConverters方法,來實現對轉換器的添加。
@Configuration public class WebConfig implements WebMvcConfigurer { /** * 添加Long轉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);
這是為什么呢?讓我們探究一下。
分析
先看一下都有什么轉換器。
可以看到在我們添加轉換器前,就已經有了2個 Mappingjackson2HttpMessageConverter 了。我們說過springboot 自帶了 HttpMessageConverter 。
那么這時出現多個,會不會相互之間有影響?
// 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轉換器列表,但是問題在于,如果有一個 HttpMessageConverter 類響應了讀寫信息,那么就會進行返回,這樣導致了后面的轉換器不生效。
解決方法
那么針對這種有幾種解決方法
- 提升自定義的消息處理轉換器優(yōu)先級;
converters.add(0,jackson2HttpMessageConverter);
- 移除列表里的springboot 默認的轉換器;
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
- 使用@EnableWebMvc 注解。慎用,會清空sprinb自帶的默認轉換器導致某些功能失效。此時的converters轉換列表為空。
@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 精度丟失問題解決的詳細內容,更多關于springboot Long 精度丟失的資料請關注腳本之家其它相關文章!
相關文章
如何解決UnsupportedOperationException異常問題
這篇文章主要介紹了如何解決UnsupportedOperationException異常問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05Java并發(fā)系列之CyclicBarrier源碼分析
這篇文章主要為大家詳細分析了Java并發(fā)系列之CyclicBarrier源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-03-03