BigDecimal被序列化后小数位丢失的处理

483 阅读3分钟

需求:

根据用户的配置的小数位来规范金额、数量的小数位显示。

问题:

由于在业务逻辑中涉及到大量的金额和数量的计算,所以在程序中对于这些字段都采用了BigDecimal来保证计算精度的准确性,但在回传给前端的数据中,由于Json序列化以后会将1.00序列化为1(默认转为数值类型),那就达不到产品所说的小数位显示的要求。

解决方案

  1. 前端一个一个获取配置属性然后再补充小数位
  2. 后端将BigDecimal字段全部转为String

解决方式

采用了第二种的方式进行转换,但此时项目开发已经到后期,如果一个个进行修改为String类型,在业务中在进行转换为BigDecimal在进行计算,这样的改动量太大,显然不太可能。所以顺着序列化这一方向,进行思考:如果我在序列化的时候 让他转为String,那不就可以了吗?这样不就可以保证小数位的不丢失了吗?

image.png

在我思来想去的时候看到了这个,时间格式化Date转String,String转Date。嗯哼哼,好像可以哟,于是点开这个注解看

image.png

image.png

竟然真可以转为String类型,于是进行了尝试,发现真的可以。那这样也就能解决序列化为String类型的问题了,如果想按照规定的配置小数位,也可以使用pattern来进行规定。

另外一种方法:

自定义一个序列化器和使用@JsonSerialize的混合双打

image.png

public class PriceToStrSerializer extends JsonSerializer<BigDecimal> {

    @Override
    public void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(scaledValue.toPlainString());
    }
	}

到这里,基本都能满足转为String的要求了。但对于我的需求而言,好像还不行,要根据配置好的小数位进行格式化。

哐哐哐,那是不是我在自定义序列化器上加上对应的注解,在注入对应的类,最后就可以操作数据库拿配置了丫。

好像有点异想天开了

image.png

image.png emmm,这个想法不对,得换,通过静态工厂注入?好像可行啊,于是哐哐哐,我又写了代码

public class PriceToStrSerializer extends JsonSerializer<BigDecimal> {

    private static RemoteMdOrgService remoteMdOrgService;
    // 无参构造方法
    public PriceToStrSerializer() {
    }

    // 静态工厂注入对应的Bean
    public static void setRemoteMdOrgService(RemoteMdOrgService service) {
        remoteMdOrgService = service;
    }
    @Override
    public void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        Long orgId = GetSysDefaultUtil.getOrgID();
        MdOrgInfoVO data = remoteMdOrgService.getMdOrgInfoByOrgId(orgId).getData();
        BigDecimal scaledValue = value.setScale(scale, RoundingMode.HALF_UP);
        jsonGenerator.writeString(scaledValue.toPlainString());
    }
	}

看到这,那不得加一个将这个值注入进来的类。

  @Configuration
    public class SerializerConfig {
        private final RemoteMdOrgService remoteMdOrgService;
        public SerializerConfig(RemoteMdOrgService remoteMdOrgService) {
            this.remoteMdOrgService = remoteMdOrgService;
            // 注入属性
            PriceToStrSerializer.setRemoteMdOrgService(remoteMdOrgService);
        }

        //全局配置时起作用,需要注册到ObjectMapper
    //    @Bean
    //    public SimpleModule bigDecimalModule() {
    //        SimpleModule module = new SimpleModule();
    //        // 注册自定义序列化器
    //        module.addSerializer(BigDecimal.class, new BigDecimalToStrSerializer());
    //        return module;
    //    }
    }

好吧 一套通过构造器注入属性 又再通过静态工厂模式 将值注入,总算拿到对应的配置值了,到这里我也总算实现了需求了。

诶,怎么加载多一点价钱数据这加载时间怎么有点长啊?

由于每次序列化,我都要对对应字段进行查库,那这样 不慢也才奇怪,于是我把这个配置值搞到了redis中,去缓存中去,最终也是加快了加载速度了

public class PriceToStrSerializer extends JsonSerializer<BigDecimal> {

    private static RemoteMdOrgService remoteMdOrgService;

    private static RedisService redisService;

    private final static String CACHE_KEY ="MdOrgInfo:";

    private final static String PRICE_KEY = "drugPriceXsw";
    // 无参构造方法
    public PriceToStrSerializer() {
    }

    // 静态工厂注入对应的Bean
    public static void setRemoteMdOrgService(RemoteMdOrgService service) {
        remoteMdOrgService = service;
    }

    public static void setRedisService(RedisService redis){redisService = redis; }

    @Override
    public void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        Long orgId = GetSysDefaultUtil.getOrgID();
        int scale = 2;
        Map<String, Object> cacheMap = redisService.getCacheMap(CACHE_KEY+orgId);
        try {
            //获取缓存不在,再去查数据库
            if(cacheMap.isEmpty()||!cacheMap.containsKey(PRICE_KEY)) {
                MdOrgInfoVO data = remoteMdOrgService.getMdOrgInfoByOrgId(orgId).getData();
                if (data != null) {
                    scale = data.getDrugPriceXsw();
                }
            }else{
                scale = (Integer) cacheMap.get(PRICE_KEY);
            }
        } catch (Exception e) {
            e.printStackTrace(); // Handle or log the exception as needed
        }
        BigDecimal scaledValue = value.setScale(scale, RoundingMode.HALF_UP);
        jsonGenerator.writeString(scaledValue.toPlainString());
    }