Spring JpaRepository: delete() with subsequent save() in the same transaction
Asked Answered
A

2

27

My entity has both autogenerated primary key (id) and business key (namespace). I need to update the record by replacing the old one. So, I'm searching it by business key, delete it and save a new entity. This works if each operation in it's own transaction. But once I put all of them in the same transaction, by the time save() is executed, delete() wasn't executed yet, so I get a constraint violation.

transactionTemplate.execute(status -> {
    MyEntity oldEntity = repository.findByNamespace(namespace);
    if (oldEntity != null) {
        repository.delete(oldEntity);
    }
    repository.save(newEntity);
    return null;
});

I actually managed to bypass it by adding

repository.flush();

But I don't really get why do I need this flush().

Angstrom answered 31/12, 2014 at 8:29 Comment(2)
I think that is because the JPA provider feels free to reorganize and/or optimize the database writes of the pending changes from the persistent context, in particular the JPA provider does not feel obliged to perform the database writes in the ordering and form implicated by the individual changes of the persistent context. Anyway the described situation is known one and the flush() one is known workaround.Carrousel
when I flush() and try to save it throws JpaObjectRetrievalFailureExceptionAnastasia
S
17

Because repository.flush() flushes the changes to the database by calling EntityManager.flush(). So when you flush the changes after delete(), sql gets executed and the following save will have no problems.

If you don't call flush it is up to persistence provider to determine when to flush the changes with transaction commit time being the deadline. Also providers don't flush changes in any particular order, so it may happen that sometimes your operation succeeds and sometimes it doesn't. Usually, providers wait until the commit time flush, but you can influence that by setting a flush mode:

for entitymanager
EntityManager.setFlushMode(FlushModeType type);

or for query
Query.setFlushMode(FlushModeType type);

There is an equivalent setting in Spring data JPA also, I'm sure, but I don't exactly know which one it is.

Note however, that immediately flushing the changes reduces the performance, so you should be careful when using it. In your particular case it is better to update the entity then it is to delete it and then persist the new one with the same business key.

Scurrile answered 31/12, 2014 at 9:35 Comment(1)
the answer does explain the problem, but not fit exactly on the provided code exampleFilbert
H
1

I faced the same thing where I needed to delete the objects first and then create a new one but was getting constraints violation exceptions because deletes were not being flushed to DB and inserts were failing.

I tried various things and variations of delete* methods but only deleteAllInBatch(Iterable<T> entity) worked.

Hem answered 23/7, 2023 at 2:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.