JPA(Hibernate实现)中EntityManager.remove后persist出现重复条目错误的原因探究
Problem
I'm using Hibernate as my JPA implementation, and I need to persist a batch of Event entities to an Event table where all share the same zipCode. The Event entity looks like this:
public class Event { String id; String zipCode; String locationCode; String eventName; String eventDesc; }
idis the primary keyzipCodeandlocationCodeform a unique constraint (UK_zipCode_locationCode)
My approach to avoid per-record checks is:
- Delete all existing
Eventrecords with the targetzipCode - Insert all new
Eventrecords for thatzipCode
Here's the initial code:
// getEventsToAdd returns list of events with zipCode "1234" List<Event> eventsToAdd = getEventsToAdd("1234"); // getEventsFromTheDB returns existing events with zipCode "1234" List<Event> oldEvents = getEventsFromTheDB("1234"); for (Event e : oldEvents) { entityManager.remove(e); } for (Event e : eventsToAdd) { entityManager.persist(e); } entityManager.flush();
This works fine when:
oldEventsis empty- All entries in
oldEventshave matchingidandzipCodeineventsToAdd
But when oldEvents contains entries whose id don't match any in eventsToAdd, I get a ConstraintViolationException with "Duplicate Entry found for key UK_zipCode_locationCode", as if the old records weren't deleted before inserting new ones.
However, if I call entityManager.flush() right after deleting the old events, everything works:
for (Event e : oldEvents) { entityManager.remove(e); } // Flush immediately after deletion entityManager.flush(); for (Event e : eventsToAdd) { entityManager.persist(e); }
Question
Why does calling flush() only at the end cause a constraint violation, but calling it right after deletion fixes the issue?
Answer
Great question—this boils down to how Hibernate's persistence context (first-level cache) works and when it sends SQL statements to the database. Let's break down the two scenarios:
1. Flushing only at the end
Hibernate doesn't execute SQL statements the moment you call remove() or persist(). Instead, it queues these operations in the persistence context to optimize performance. When you finally trigger flush(), Hibernate reorders the SQL statements by default to follow this sequence:
- Inserts first
- Updates next
- Deletes last
That's the critical issue here! Even though you wrote the delete logic first in your code, Hibernate prioritizes running INSERT statements before DELETE statements. So when you insert a new Event with a zipCode/locationCode pair that's still present in the old records (since deletes haven't been executed yet), the unique constraint gets violated.
This reordering is Hibernate's default optimization, but it clashes with your use case where deletes need to happen before inserts to avoid constraint conflicts.
2. Flushing right after deletion
Calling entityManager.flush() immediately after the delete loop forces Hibernate to send all the DELETE SQL statements to the database right away. Now, the old records are actually removed from the table before you start persisting the new events. When you later call persist() for the new entities, their INSERT statements won't conflict with any existing records, so the unique constraint is satisfied.
Extra Tips
- If you want to avoid manual flushes, you could adjust Hibernate's flush mode or operation ordering, but flushing after deletion is a straightforward, reliable fix for this scenario.
- Double-check that
getEventsFromTheDB()fetches entities within the same persistence context—if you fetched them in a separate context,remove()calls might not queue correctly. But since adding the flush fixed your issue, this isn't the problem here.
内容的提问来源于stack exchange,提问作者ajay

