java BigDecimal插入数据库小数位丢失问题,精度丢失等问题

跟踪发现 scale 为2时会有问题,如果为4就会保留小数位

为什么会丢精度(35.35 → 35)

  1. SQLServer money 本质

    • money = 固定 DECIMAL(19,4)(小数点后强制 4 位
    • 但它是数据库专有类型,不是标准 DECIMAL/NUMERIC
    • 当驱动 / MyBatis 没正确指定精度 /scale 时,会被当成整数截断
  2. MyBatis 默认映射问题

    • 默认 BigDecimalTypeHandler 对应 NUMERIC/DECIMALMyBatis
    • 不认识 money没传 scale 信息 → SQLServer 按 默认 scale=0 处理
    • 批量插入时更严重:按最小精度统一截断
  3. 驱动(mssql-jdbc)行为

    • 低版本驱动:setBigDecimalmoney丢失 scale
    • 高版本需要加连接参数:calcBigDecimalPrecision=true

结果:BigDecimal(35.35, scale=2) → 被当成 35 插入

自定义 修改拦截器(一劳永逸)

写一个专门给 Bigdecimal插入、修改拦截器在即将插入前将精度提高解决:


import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@Component
@Intercepts({
        // 拦截 增/删/改 最顶层执行器
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class GlobalBigDecimalInterceptor implements Interceptor {

    private static final int TARGET_SCALE = 4;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object parameter = invocation.getArgs()[1]; // 这是 MyBatis 真正的参数!

        if (parameter != null) {
            // 【全局处理所有 BigDecimal】打断点这里 100% 进来!
            processAllBigDecimal(parameter);
        }

        return invocation.proceed(); // 处理完再执行 SQL
    }

    // 递归处理一切对象:实体、Map、List、嵌套对象
    private void processAllBigDecimal(Object obj) {
        if (obj == null) return;

        // 1. 处理 List / 批量插入
        if (obj instanceof List<?>) {
            ((List<?>) obj).forEach(this::processAllBigDecimal);
            return;
        }

        // 2. 处理 MyBatis 包装参数 ParamMap
        if (obj instanceof MapperMethod.ParamMap<?>) {
            MapperMethod.ParamMap<?> map = (MapperMethod.ParamMap<?>) obj;
            for (Object key : map.keySet()) {
                processAllBigDecimal(map.get(key));
            }
            return;
        }

        // 3. 处理 MyBatis 内部 Map
        if (obj instanceof DefaultSqlSession.StrictMap<?>) {
            DefaultSqlSession.StrictMap<?> map = (DefaultSqlSession.StrictMap<?>) obj;
            for (Object key : map.keySet()) {
                processAllBigDecimal(map.get(key));
            }
            return;
        }

        // 4. 处理普通 Map
        if (obj instanceof Map<?, ?>) {
            Map<?, ?> map = (Map<?, ?>) obj;
            for (Object key : map.keySet()) {
                Object val = map.get(key);
                if (val instanceof BigDecimal) {
                    ((Map) map).put(key, fixScale((BigDecimal) val));
                } else {
                    processAllBigDecimal(val);
                }
            }
            return;
        }

        // 5. 处理实体类(你的对象一定走这里)
        Class<?> clazz = obj.getClass();
        if (clazz.getName().startsWith("java.")) return;

        // 遍历当前类 + 父类所有字段
        while (clazz != null && !clazz.getName().startsWith("java.lang")) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (field.getType() == BigDecimal.class) {
                    try {
                        field.setAccessible(true);
                        BigDecimal value = (BigDecimal) field.get(obj);
                        if (value != null) {
                            // 🔥 这里你打断点,能亲眼看到 35.35 → 35.3500
                            field.set(obj, fixScale(value));
                        }
                    } catch (Exception ignored) {}
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

    // 精度统一处理
    private BigDecimal fixScale(BigDecimal decimal) {
        if (decimal.scale() == TARGET_SCALE) return decimal;
        return decimal.setScale(TARGET_SCALE, RoundingMode.HALF_UP);
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {}
}

解决方法2 cast函数直接强转即可 或调整csale精度大点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北凉军

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值