BigDecimal/Long 前後端互動失去精度解決方法
阿新 • • 發佈:2018-11-25
問題
發現一個詭異的現象,資料庫儲存的bigDecimal型別的資料,經過springboot返回給前端資料丟失了幾位小數,例如 222233334444.01134567(後端)>222233334444.01135(前端)。經過查資料,在Controller層通過@ResponseBody將返回資料自動轉換成json時,不做任何處理,而直接傳給前端的話,在BigDecimal長度大於17位(不包括小數點)會出現精度丟失,在Long長度大於17位時也會出現精度丟失的問題。
解決方案
- 類屬性直接定義成String型別,處理好之後再返回前端
- 自定義訊息轉換器
- 在待轉化的欄位之上加上@JsonSerialize(using=ToStringSerializer.class)
1和3是類似的做法,如果系統已經成型,這兩種做法會改動很多地方,這裡著重記錄一下第2種做法。
直接貼程式碼
package ***.***.***.config; import ***.***.***.constant.PathPattern; import ***.***.***.interceptor.TokenInterceptor; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.io.IOException; import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.List; @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { @Autowired private TokenInterceptor tokenInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { /** * 新增請求鑑權攔截器 */ registry.addInterceptor(tokenInterceptor) .addPathPatterns(PathPattern.API_ENTRY_POINT) .excludePathPatterns(PathPattern.NO_AUTH_API_ENTRY_POINT); } @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer.favorPathExtension(false); } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(toStringConverter()); } /** * BigDecimal Long 轉化為String * @return */ @Bean public MappingJackson2HttpMessageConverter toStringConverter() { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); ObjectMapper mapper = new ObjectMapper(); SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(BigDecimal.class, BigDecimalToStringSerializer.instance); simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); simpleModule.addSerializer(long.class, ToStringSerializer.instance); mapper.registerModule(simpleModule); converter.setObjectMapper(mapper); return converter; } @JacksonStdImpl static class BigDecimalToStringSerializer extends ToStringSerializer { public final static BigDecimalToStringSerializer instance = new BigDecimalToStringSerializer(); public BigDecimalToStringSerializer() { super(Object.class); } public BigDecimalToStringSerializer(Class<?> handledType) { super(handledType); } @Override public boolean isEmpty(SerializerProvider prov, Object value) { if (value == null) { return true; } String str = ((BigDecimal) value).stripTrailingZeros().toPlainString(); return str.isEmpty(); } @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeString(((BigDecimal) value).stripTrailingZeros().toPlainString()); } @Override public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException { return createSchemaNode("string", true); } @Override public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSer) throws IOException { // no type info, just regular serialization serialize(value, gen, provider); } } }
- addInterceptors:是配置攔截器
addInterceptor:註冊攔截器
addPathPatterns:新增已註冊攔截器應用於的URL
excludePathPatterns:添加註冊攔截器不應該應用於的URL - configureContentNegotiation:ViewResolver使用所請求的媒體型別的一個實現
favorPathExtension:是否應使用URL路徑中的路徑擴充套件來確定所請求的媒體型別。 - configureMessageConverters:自定義轉換器
toStringConverter:增加了序列化long及包裝類Long需要使用ToStringSerializer轉成String型別
BigDecimalToStringSerializer:因為直接用ToStringSerializer,BigDecimal的值過小就會顯示科學計數法,也不能去除末尾無效的零。這邊自定義一個BigDecimalToStringSerializer繼承自ToStringSerializer,修改裡面的serialize、isEmpty方法。
在這裡還有種延伸的做法,如果前端對BigDecimal返回的小數位有多種要求,我們可以自定義幾種資料型別extends BigDecimal,自定義多種BigDecimalToStringSerializer,然後在訊息轉換器中分別按型別處理,避免在程式中頻繁去寫格式。