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

为何调用entityManager.clear()会导致JPA测试日期值不一致?

日期不匹配问题:调用entityManager.clear()后数据库读取日期与预期不符

运行@DataJpaTest时,在setUp方法中调用entityManager.clear()将Book实体从持久化上下文分离后,后续测试出现日期不匹配错误:预期publishDate为1813-01-28,实际查询结果为1813-01-27。注释掉entityManager.clear()后测试即可通过,且未修改任何Book数据,需排查该现象的原因。

错误信息

比较整个Book实体时的错误

BookRepositoryTest > doSomething() FAILED
    org.opentest4j.AssertionFailedError: expected: <Book(id=6, title=Pride and Prejudice, price=16.99, quantity=12, author=Jane Austen, ISBN=890-1234567890, genre=Romance, publishDate=1813-01-28)> but was: <Book(id=6, title=Pride and Prejudice, price=16.99, quantity=12, author=Jane Austen, ISBN=890-1234567890, genre=Romance, publishDate=1813-01-27)>
        at app//org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
        at app//org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
        at app//org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
        at app//org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
        at app//org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
        at app//org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1145)
        at app//library.backend.api.BookRepositoryTest.doSomething(BookRepositoryTest.java:55)

仅比较日期时的错误

BookRepositoryTest > doSomething() FAILED
    org.opentest4j.AssertionFailedError: The dates do not match ==> expected: <1813-01-28> but was: <1813-01-27>
        at app//org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
        at app//org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
        at app//org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
        at app//org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
        at app//org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1156)
        at app//library.backend.api.BookRepositoryTest.doSomething(BookRepositoryTest.java:55)

测试代码

@ExtendWith(SpringExtension.class)
@DataJpaTest
@ActiveProfiles(profiles = {"test"})
public class BookRepositoryTest {
    @Autowired
    private BookRepository bookRepository;

    @Autowired
    private TestEntityManager entityManager;

    private ObjectMapper mapper;
    
    @Value("classpath:books.json")
    Resource booksJson;

    List<Book> books;

    @BeforeEach
    void setUp() throws Exception{
        mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        books = mapper.readValue(booksJson.getInputStream(),
                            new TypeReference<List<Book>>(){});
        books.forEach(entityManager::persistAndFlush);
        entityManager.clear();
    }

    @Test
    void doSomething(){
        List<Book> firstSixBooks = bookRepository.findAll(PageRequest.of(0,6)).toList();
        assertEquals(6, firstSixBooks.size());
        for(int j=0; j<6; j++){
            assertEquals(books.get(j).getPublishDate(), firstSixBooks.get(j).getPublishDate());
        }
    }
}

原因分析

  1. 时区不统一:

    • 未调用entityManager.clear()时,测试直接使用持久化上下文内存中的Book对象,日期是JSON反序列化后的原始值(可能使用了特定时区,如UTC)。
    • 调用clear()后,测试从数据库重新加载数据,若JPA/Hibernate读取日期时使用的时区与JSON序列化时的时区不一致,会导致日期偏移一天。例如,JSON反序列化用UTC,数据库读取用本地时区(如东八区),跨时区转换可能让日期往前偏移一天。
  2. 日期类型映射差异:

    • 若Book实体的publishDate字段用LocalDate(无时区),但数据库列类型是带时区的TIMESTAMP WITH TIME ZONE,Hibernate在持久化时会自动填充JVM时区信息,读取时转换为LocalDate时可能因时区差导致日期变化。
    • 若数据库列类型是DATE,但JPA配置的时区与序列化时区不一致,也会出现转换偏差。
  3. 持久化上下文缓存机制:

    • 持久化上下文会缓存已加载的实体,未clear()时直接返回缓存中的对象,日期保持原始值;clear()后缓存清空,必须从数据库读取,此时经过Hibernate的日期转换逻辑,导致日期变更。

解决建议

  • 统一时区配置:在application-test.yml中添加以下配置,确保序列化和数据库读写使用同一时区(推荐UTC):
    spring:
      jackson:
        time-zone: UTC
      jpa:
        properties:
          hibernate:
            jdbc:
              time_zone: UTC
    
  • 检查实体与数据库列类型:确保publishDate字段为LocalDate,数据库对应列类型为DATE,避免时区干扰。
  • 显式指定ObjectMapper时区:在setUp方法中为ObjectMapper设置时区,与JPA配置保持一致:
    mapper.setTimeZone(TimeZone.getTimeZone("UTC"));
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 14:59:51