Skip to main content

Command Palette

Search for a command to run...

🐢 Why Your JPA Transaction Slowed Down After Adding flush() and clear() (And How to Fix It)

Published
4 min read
🐢 Why Your JPA Transaction Slowed Down After Adding flush() and clear() (And How to Fix It)

If you’ve ever added entityManager.flush() and entityManager.clear() inside a Spring Boot JPA transaction to “force visibility” after deletes and inserts — and then watched your app slow to a crawl — this post is for you.

Let’s unpack what’s really happening, why performance drops, and what you can do about it.


💥 The Scenario

You have a transactional method that deletes old records and inserts new ones into the same table:

@Transactional
public void refreshData() {
    myRepository.deleteByType("A");
    entityManager.flush();
    entityManager.clear();

    List<MyEntity> newRecords = fetchNewData();
    myRepository.saveAll(newRecords);
}

You added flush() and clear() because inserts weren’t seeing the effects of the delete.
But after that, the transaction time exploded from milliseconds to seconds.


🧠 What’s Really Going On

🔹 flush(): Forces Immediate SQL Execution

Normally, Hibernate:

  • Keeps track of changes in memory

  • Batches SQL operations for efficiency

  • Executes them just before COMMIT

When you call entityManager.flush(), you force Hibernate to:

Push all pending changes to the database right now.

This breaks batching and causes each statement to hit the DB individually — increasing round-trips and latency.


🔹 clear(): Wipes the Persistence Context

When you call entityManager.clear(), Hibernate:

Detaches all managed entities.

That means:

  • The first-level cache is gone

  • Hibernate must re-fetch entities for any further access

  • All dirty-checking snapshots are lost

In other words, you just told Hibernate to forget everything it knows about this transaction.


🐌 Why Performance Drops Hard

CauseEffect
flush() executes SQL earlyBatching is disabled; more DB round-trips
clear() detaches entitiesCache lost; Hibernate must reselect data
Same-table delete + insertLocks and index contention on DB
Large persistence contextHibernate keeps snapshots for all managed entities
Cascades and relationshipsTrigger extra SQL during flush

So, the combination of forced flush + clear in the middle of a transaction is like slamming the brakes and restarting the engine mid-race.


🧰 Smarter Alternatives

✅ 1. Use Bulk Deletes Instead of Entity Deletes

@Modifying
@Query("DELETE FROM MyEntity e WHERE e.type = :type")
void deleteByType(@Param("type") String type);

This executes a single SQL DELETE without loading entities into memory — skipping cascades and dirty checking.

Result: Much faster deletes.


✅ 2. Split Transactions Logically

If your inserts don’t depend on Hibernate’s in-memory context, separate the delete and insert phases:

@Transactional
public void refreshData() {
    myRepository.deleteByType("A");
    insertNewRecordsInNewTx();
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertNewRecordsInNewTx() {
    myRepository.saveAll(fetchNewData());
}

The first transaction commits before the second begins — so inserts see the fresh state, without flush() or clear() hacks.


✅ 3. Batch Inserts for Large Data Loads

Configure batching in application.properties:

spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true

Then, in code:

int i = 0;
for (MyEntity e : newRecords) {
    entityManager.persist(e);
    if (++i % 50 == 0) {
        entityManager.flush();
        entityManager.clear();
    }
}

This way, you keep control of batching — and flush() only happens in controlled intervals.


✅ 4. Detach Selectively, Not Entirely

Instead of clearing everything:

entityManager.detach(entity);

This detaches only one object, not the entire context — preserving Hibernate’s caching and relationships.


⚙️ Advanced Tip: Use Native SQL for Full Refresh Jobs

When dealing with large, replace-all style operations (like refreshing cache tables or syncing external data):

@Modifying
@Query(value = "DELETE FROM my_table WHERE type = :type", nativeQuery = true)
void bulkDelete(@Param("type") String type);

@Modifying
@Query(value = "INSERT INTO my_table (id, type, value) VALUES (:id, :type, :value)", nativeQuery = true)
void bulkInsert(...);

Hibernate is great for ORM, but not always the best fit for bulk data processing.
Native SQL gives you full control and avoids the persistence context overhead entirely.


🧩 TL;DR Summary

ProblemRoot CauseRecommended Fix
Transaction becomes slowForced flush disables batchingLet Hibernate flush automatically
Extra queriesContext clearedDetach selectively or split transactions
Lock contentionSame-table delete/insertSeparate transactions or bulk SQL
Memory usage highLarge persistence contextBatch operations with periodic flush/clear
Too many cascadesEntity deleteUse bulk JPQL delete

🚀 Key Takeaways

  • Avoid calling flush() + clear() mid-transaction unless you absolutely have to.

  • Use bulk queries or split transactions for visibility issues.

  • Configure batching for heavy inserts.

  • Hibernate is smart — but only if you let it handle flushing and caching efficiently.


💬 Let’s Discuss

Have you faced similar performance drops after using flush() or clear()?
How did you solve it — batching, native SQL, or separate transactions?

Drop your story in the comments — it might save someone hours of debugging.


🔖 Tags:

#springboot #jpa #hibernate #java #performance


Would you like me to:

  • Add a cover image + short excerpt (for the Hashnode header and SEO card), or

  • Generate Hashnode frontmatter (YAML metadata) for copy-paste publishing?

That would make it 100% post-ready.