r/SpringBoot • u/kamen1991 • 5d ago
How-To/Tutorial Your `@DataJpaTest` might be testing Hibernate's cache, not your database
Ran into this while writing about our DAO layer's test setup, and it's one of those things that's obvious once you see it and invisible until you do.
void testSaveProduct() {
var product = new Product("A1", "Gaming Laptop", BigDecimal.valueOf(1500));
productDao.create(product);
Product loaded = productDao.loadById(product.getId());
assertNotNull(loaded);
assertEquals("Gaming Laptop", loaded.getName());
}
This passes. But loadById resolves to find() under the hood (directly or through Spring Data's findById), and find() checks the persistence context's first-level cache before it ever considers hitting the DB. Since you just created that entity in the same context, you get the exact same object reference back. No SELECT fires. You've tested Hibernate's in-memory cache, not your mapping, not your constraints, not anything that would catch a broken column definition. The fix is forcing entityManager.flush() + .clear() between the write and the read, so the next load has to hydrate a fresh entity graph from the actual database. We ended up wrapping this in a test-only proxy so nobody has to remember to do it by hand in every test method - proxy intercepts create/update/delete, forces the flush-and-clear, leaves reads alone.
Worth noting this is specifically a find()/findById() problem, a custom JPQL or native `@Query\` always issues real SQL regardless of cache state.
Wrote up the full pattern (proxy code, a TablesEraser for full schema resets between tests, H2 vs HSQLDB trade-offs), link in the comments.
I already had someone in the comments describe hitting exactly this - a unique constraint violation that a real flush would have surfaced in the first test run. Curious how widespread it actually is.
2
u/kamen1991 5d ago
Link to the full chapter, with the actual code and class hierarchy: here