DEV Community

Cover image for 🐢 Why Your JPA Transaction Slowed Down After Adding 'flush()' and 'clear()' (And How to Fix It)
araf
araf

Posted on

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

Have you ever added entityManager.flush() and entityManager.clear() inside a Spring Boot JPA transaction to make deletes and inserts “reflect immediately” — only to see your performance tank?

You're not alone.
This is one of those subtle Hibernate behaviors that bites even experienced developers.

Let’s unpack why it happens, and what to do instead.


🧩 The Real-World Scenario

You have a Spring Boot service method like this:

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

    List<MyEntity> newRecords = fetchNewData();
    myRepository.saveAll(newRecords);
}
Enter fullscreen mode Exit fullscreen mode

You added flush() and clear() because deletes weren’t visible before inserts — and it “fixed” the logic.

But now, your transaction runs 3× slower, the DB CPU spikes, and Hibernate logs show hundreds of SQL statements being executed one by one.

What’s going on?


⚙️ Understanding flush() and clear()

🔹 flush()

Tells JPA:

“Send all pending SQL statements to the database now.”

Normally, Hibernate batches and optimizes SQL statements until transaction commit.
When you call flush(), it forces immediate execution — breaking batching and hitting the database right away.

🔹 clear()

Tells JPA:

“Forget everything you know about the current persistence context.”

This detaches all managed entities.
Hibernate now has to re-fetch them from the database if you reference them again, and you lose the in-memory first-level cache.


⚠️ Why It Becomes Slow

Cause Effect
flush() executes pending SQL immediately Hibernate loses batching and optimization
clear() wipes first-level cache Every next entity access triggers a new SELECT
Same-table deletes + inserts DB locks and index contention
Large transaction scope Hibernate keeps snapshots for dirty checking
Cascades or orphan removal Extra SQLs during flush

In short — you gain “visibility” but lose performance and batching efficiency.


💡 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);
Enter fullscreen mode Exit fullscreen mode

Bulk deletes:

  • Run a single SQL DELETE
  • Don’t load entities into memory
  • Don’t trigger cascade or dirty checking

Perfect for refreshing data tables.


✅ 2. Split the Transaction Logically

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

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

Now, the delete commits before the insert starts — no need to manually flush() or clear().


✅ 3. Use Batching for Inserts

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
Enter fullscreen mode Exit fullscreen mode

Then in code:

int i = 0;
for (MyEntity entity : newRecords) {
    entityManager.persist(entity);
    if (++i % 50 == 0) {
        entityManager.flush();
        entityManager.clear();
    }
}
Enter fullscreen mode Exit fullscreen mode

You’ll get massive performance gains for large datasets.


✅ 4. Avoid clear() Unless You Really Need It

Instead of clearing everything:

entityManager.detach(entity);
Enter fullscreen mode Exit fullscreen mode

This detaches only the specific entity you need, preserving most of the persistence context.


🚀 The TL;DR Summary

Problem Cause Fix
Transaction runs slowly flush() executes SQL immediately Avoid frequent flushes
Too many selects clear() wipes cache Detach selectively
Delete + insert on same table DB contention Split into separate transactions
High memory usage Large persistence context Batch + periodic flush/clear
Cascades killing performance Entity-based delete Use bulk delete query

🧠 Key Takeaway

Don’t reach for flush() + clear() to “fix visibility.”
They’re low-level hammers that often break performance and caching.

Instead:

  • Use bulk operations
  • Batch inserts
  • Split transactions when visibility matters
  • Or use native SQL when you’re replacing data wholesale

Your Spring Boot + JPA app will thank you with faster, cleaner transactions.


💬 What about you?

Have you run into flush() / clear() performance issues before?
How did you handle them — batching, bulk queries, or native SQL?

Drop your experience below 👇 — it helps others avoid the same trap.

Top comments (0)