You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hibernate调用findAll()5次后BigDecimal自动添加小数部分问题

Hibernate缓存引发BigDecimal字段0变0.0的问题分析与解决

问题现象

在Hibernate(6.2.7/5.8)、SpringBoot(3.1.3/2.7)搭配PostgreSQL的环境中,出现了可复现的异常:数据库中存储值为0的BigDecimal字段,循环调用TestRepository.findAll()时,前5次返回的实体中该字段显示为0,从第6次开始自动变成0.0。跨测试执行时,若用严格断言(比如直接比较字符串或依赖BigDecimal的equals方法,该方法会校验小数位),会随机导致测试失败。

原因拆解

这个问题确实和Hibernate一级缓存(L1缓存)有关,但核心不是缓存修改了底层数据,而是两点叠加导致的:

  1. BigDecimal的特性:BigDecimal是不可变类,其equals方法会同时比较数值和小数位(scale),0(scale=0)和0.0(scale=1)数值相等,但equals返回false,toString输出也不同。
  2. 缓存与JDBC加载逻辑:
    • 前5次查询时,实体从Hibernate一级缓存直接获取,保持了首次从数据库加载时的BigDecimal(0)(小数位为0)。
    • 第6次开始,Hibernate可能触发了缓存刷新、会话重建(比如Spring Data JPA在无事务注解时,每次查询会新建EntityManager),此时重新从数据库加载数据时,PostgreSQL JDBC驱动返回了带有小数位的BigDecimal(0.0),导致后续输出变化。

解决方案

针对这个问题,有几种实用的解决思路:

1. 统一实体字段的小数位配置

在实体类的BigDecimal字段上通过@Column指定固定的精度和小数位,强制Hibernate以统一格式存储和加载数据:

@Column(precision = 10, scale = 1) // 可根据业务需求调整精度和小数位
private BigDecimal value;

这样无论数据库中存储的是0还是0.0,加载后都会统一为指定小数位的BigDecimal,避免输出不一致。

2. 自定义Hibernate类型处理BigDecimal

创建自定义类型,在加载数据时统一BigDecimal的小数位,确保输出一致:

public class FixedScaleBigDecimalType extends BigDecimalType {
    @Override
    public BigDecimal nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
        BigDecimal value = super.nullSafeGet(rs, position, session, owner);
        if (value != null) {
            // 统一设置小数位为1,可根据业务调整, rounding模式按需选择
            return value.setScale(1, RoundingMode.HALF_UP);
        }
        return null;
    }
}

然后在实体字段上指定使用该类型:

@Type(FixedScaleBigDecimalType.class)
private BigDecimal value;

3. 调整测试断言逻辑

不要依赖toString输出或equals方法(会校验小数位),改用compareTo方法比较数值是否相等:

// 正确断言:仅比较数值是否相等,忽略小数位差异
Assert.assertEquals(0, testEntity.getValue().compareTo(BigDecimal.ZERO));

// 如果业务要求固定小数位,可先统一小数位再比较
Assert.assertEquals(BigDecimal.ZERO.setScale(1), testEntity.getValue());

4. 禁用一级缓存(不推荐)

可以通过查询注解禁用一级缓存,但会降低查询性能,仅作为临时调试手段:

@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "false"))
List<TestEntity> findAll();

总结

这个问题本质是Hibernate缓存机制与JDBC驱动对BigDecimal小数位的处理差异导致的,核心矛盾是数值相等但小数位不同。最稳妥的解决方式是统一BigDecimal的小数位配置,或者调整断言逻辑避免不必要的严格校验。

内容的提问来源于stack exchange,提问作者Lukáš Gemela

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 15:26:03