DEV Community

Satyaki Saha
Satyaki Saha

Posted on

Silent Saves: How JPA Persists Your Changes Without You Asking

One of our junior devs wrote an API that was supposed to be “read-only” and act as an internal data funnel — pull some records, transform them, pass them on. nothing that should ever come close to the database. But it corrupted the production data badly.

Then I started to dig into the code, and saw that he had @Transactional on the function. He was retrieving entities internally via EntityManager.find() (or a JPQL query, same thing) and mutating some fields on them in the middle of processing, just as a way to store intermediate state while composing the response. No save, no merge, no repository.update() anywhere in sight, which is exactly what made him feel it was safe.

But because the entities were managed and the method ran inside an active transaction, every one of those "intermediate state" field mutations got picked up by Hibernate's dirty checking at commit time. What he thought was a harmless, read-only data funnel was silently issuing UPDATE statements against production data — overwriting real records with transient values that were only ever meant to exist in memory for the duration of the request.

The fix was intuitive at that point: mark the transaction readOnly = true (@Transactional(readOnly = true)), which tells Hibernate to skip dirty checking and flushing entirely for that context. But 2 things I learnt were Hibernate engineering is a masterclass in abstraction, and learning hibernate is go deep or go home case.

Top comments (0)