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

Java 17迁移后JUnit测试中LocalDateTime精度不匹配问题

Java 17迁移后LocalDateTime精度导致单元测试失败的问题

问题现象

此前在Java 11 + Docker PostgreSQL 12环境下运行正常的单元测试,迁移至Java 17后大量失败,核心问题为LocalDateTime类型的精度不匹配。测试报错示例:

[ERROR] UnitTest.test1:334
expected: CustomType (3185, 1, Description, 2022-12-01T22:10:27.924910854, 2022-12-01T22:10:27.924951801, user, false)
but was: CustomType (3185, 1, Description, 2022-12-01T22:10:27.924911, 2022-12-01T22:10:27.924952, user, false)
[ERROR] UnitTest.test2:60
expected: CustomType2 (3182, Test, Hello, 2022-12-01T22:10:27.653447384, user, index, null, null, false, null)
but was: CustomType2 (3182, Test, Hello, 2022-12-01T22:10:27.653447, user, index, null, null, false, null)
[ERROR] UnitTest.test3:268
expected: CustomType (3184, 1, Description, 2022-12-01T22:10:27.863401246, 2022-12-01T22:10:27.863438735, user, false)
but was: CustomType (3184, 1, Description, 2022-12-01T22:10:27.863401, 2022-12-01T22:10:27.863439, user, false)

测试逻辑:手动实例化jOOQ自动生成的对象作为预期值,存入PostgreSQL(对应字段类型为timestamp)后取出对比,jOOQ生成的实体类中时间字段为LocalDateTime而非Instant。

单元测试代码

// Unit Test
CustomType instance = CustomType.builder()
    // ...其余字段省略
    .lastUpdatedAt(LocalDateTime.now())
    .build();

CustomType result = repository.insert(instance);
instance.setId(result.getId());
Assertions.assertThat(result).isEqualTo(instance); // AssertJ

Repository代码

// Repository
public CustomType insert(CustomType data) {
  data.setId(null);
  CustomTypeRecord record = new CustomTypeRecord(); // ...此处使用record无需关注 ;)
  record.from(data);
  data = mainDsl.insertInto(CUSTOM_TYPE)
      .set(record)
      .returning()
      .fetchOneInto(CustomType.class);
  return data;
}

已确认:源码无自定义LocalDateTime格式化逻辑,jOOQ已升级至3.17.5,核心依赖如下:

$ ./mvnw dependency:list
...
[INFO] --- maven-dependency-plugin:3.3.0:list (default-cli) @ graphql-api ---
[INFO]
[INFO] The following files have been resolved:
[INFO]    org.springframework.boot:spring-boot-starter-jooq:jar:2.7.4:compile -- module spring.boot.starter.jooq [auto]
[INFO]    org.springframework.boot:spring-boot-starter-jdbc:jar:2.7.4:compile -- module spring.boot.starter.jdbc [auto]
[INFO]    com.zaxxer:HikariCP:jar:4.0.3:compile -- module com.zaxxer.hikari
[INFO]    org.springframework:spring-jdbc:jar:5.3.23:compile -- module spring.jdbc [auto]
[INFO]    jakarta.activation:jakarta.activation-api:jar:1.2.2:compile -- module jakarta.activation
[INFO]    jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:compile -- module java.xml.bind
[INFO]    org.springframework:spring-tx:jar:5.3.23:compile -- module spring.tx [auto]
[INFO]    org.springframework:spring-core:jar:5.3.23:compile -- module spring.core [auto]
[INFO]    org.springframework:spring-jcl:jar:5.3.23:compile -- module spring.jcl [auto]
[INFO]    org.jooq:jooq:jar:3.17.5:compile -- module org.jooq
[INFO]    io.r2dbc:r2dbc-spi:jar:0.9.1.RELEASE:compile -- module r2dbc.spi [auto]
[INFO]    org.reactivestreams:reactive-streams:jar:1.0.4:compile -- module org.reactivestreams [auto]
[INFO]    org.jooq:jooq-codegen:jar:3.17.5:compile -- module org.jooq.codegen
[INFO]    org.jooq:jooq-meta:jar:3.17.5:compile -- module org.jooq.meta
[INFO]    org.jooq:jooq-postgres-extensions:jar:3.17.5:compile -- module org.jooq.postgres.extensions
[INFO]    org.springframework.boot:spring-boot-starter-web:jar:2.7.4:compile -- module spring.boot.starter.web [auto]
...

问题原因

  1. Java版本对LocalDateTime精度的处理差异:Java 11中LocalDateTime.now()默认保留纳秒级精度,但Java 17下,部分JDK实现或底层依赖(如JDBC驱动)与PostgreSQL交互时,会对纳秒精度做截断或四舍五入处理;
  2. PostgreSQL timestamp类型的精度限制:PostgreSQL的timestamp默认仅支持微秒级(6位小数)存储,而Java的LocalDateTime支持纳秒级(9位小数),存入数据库时会自动截断到微秒,取出后精度与预期值不匹配;
  3. JDBC驱动版本适配问题:Java 17下,旧版PostgreSQL JDBC驱动的精度处理逻辑可能与Java 11存在差异,导致取出的LocalDateTime精度不符合预期。

解决办法

方案1:统一精度,截断预期值到微秒

在创建预期值时,主动将LocalDateTime截断到微秒,匹配数据库存储精度:

CustomType instance = CustomType.builder()
    // ...其余字段省略
    .lastUpdatedAt(LocalDateTime.now().truncatedTo(ChronoUnit.MICROS))
    .build();

方案2:调整数据库字段精度(业务允许时)

将PostgreSQL中的timestamp字段改为timestamp(9),支持纳秒级存储,确保存入和取出的精度一致:

ALTER TABLE custom_type ALTER COLUMN last_updated_at TYPE timestamp(9);

方案3:自定义断言逻辑,忽略精度差异

如果无需修改业务代码或数据库,可在断言时忽略LocalDateTime的纳秒部分:

// 方式1:递归比较时忽略时间字段,单独比较精度
Assertions.assertThat(result)
    .usingRecursiveComparison()
    .ignoringFields("lastUpdatedAt", "createdAt") // 替换为实际时间字段名
    .isEqualTo(instance);
Assertions.assertThat(result.getLastUpdatedAt().truncatedTo(ChronoUnit.MICROS))
    .isEqualTo(instance.getLastUpdatedAt().truncatedTo(ChronoUnit.MICROS));

// 方式2:直接自定义时间比较规则
Assertions.assertThat(result.getLastUpdatedAt())
    .isCloseTo(instance.getLastUpdatedAt(), within(1, ChronoUnit.MICROS));

方案4:升级JDBC驱动至Java 17兼容版本

确认PostgreSQL JDBC驱动版本是否适配Java 17,建议升级到42.5.0及以上的稳定版本,避免版本兼容导致的精度处理差异。


内容的提问来源于stack exchange,提问作者user4695271

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 15:30:50