Hibernate调用findAll()5次后BigDecimal自动添加小数部分问题
问题现象
在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缓存)有关,但核心不是缓存修改了底层数据,而是两点叠加导致的:
- BigDecimal的特性:
BigDecimal是不可变类,其equals方法会同时比较数值和小数位(scale),0(scale=0)和0.0(scale=1)数值相等,但equals返回false,toString输出也不同。 - 缓存与JDBC加载逻辑:
- 前5次查询时,实体从Hibernate一级缓存直接获取,保持了首次从数据库加载时的
BigDecimal(0)(小数位为0)。 - 第6次开始,Hibernate可能触发了缓存刷新、会话重建(比如Spring Data JPA在无事务注解时,每次查询会新建EntityManager),此时重新从数据库加载数据时,PostgreSQL JDBC驱动返回了带有小数位的
BigDecimal(0.0),导致后续输出变化。
- 前5次查询时,实体从Hibernate一级缓存直接获取,保持了首次从数据库加载时的
解决方案
针对这个问题,有几种实用的解决思路:
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

