0

Setup: SpringBoot application using SpringJPA with Hibernate

I have two entities: A and B. Set is a child of A.

I want to remove a B and create another one in a characteristic which would hit a unique constraint. To avoid that I first need to delete the old B before creating a new one. Since I am in a transaction, I have to flush after deletion.

Executing the following code will still fail:

    @Transactional
    void removeOldBsAndCreateNewOnes(A a) {
        final Set<B> bSet = a.getBs();
        final Set<B> removedBs = new HashSet<>();
        bSet.stream()
                .filter(
                    //magic stuff here
                )
                .forEach(b -> {
                    bRepository.delete(b);
                    // Flush is needed as we are in a transaction and not flushing will make hibernate to insert the new B first on commit which will violate the configured unique constraints.
                    bRepository.flush();
                    removedBs.add(b);
                });
        bSet.removeAll(removedBs);
    
        final Set<B> newBs = new HashSet<>();
        // Doing some stuff to detemine new Bs
        bRepository.saveAll(newBs);
        bSet.addAll(newBs);
    }

Nevertheless I get a unique constraint violation error. flush() is not executed. I already searched for reasons and looked here without success:

1 Answer 1

0

Because of some strange solutions which only cause much more problems (e.g. creating a manual delete query using @Modifying causes the EntityManager to explode or doing delete and create in separate transactions) I would like to document what I've found out:

Before a refactoring everything worked like expected: flush() was executed and the entity immediately got deleted. After weeks of the refactoring step the bug came up and nobody knew why.

Solution: You need to remove the entity from the parent first. Otherwise Hibernate will not execute flush().
Example:

        bSet.stream()
                .filter(
                    //magic stuff here
                )
                .forEach(b -> {
                    bRepository.delete(b);
                    // Flush is needed as we are in a transaction and not flushing will make hibernate to insert the new B first on commit which will violate the configured unique constraints.
                    removedBs.add(b);
                });
        bSet.removeAll(removedBs);
        bRepository.flush();

        // Create new Bs

Or in a correct order:

        bSet.stream()
                .filter(
                    //magic stuff here
                )
                .forEach(b -> {
                    bSet.remove(b);
                    bRepository.delete(b);
                });
        // Flush is needed as we are in a transaction and not flushing will make hibernate to insert the new B first on commit which will violate the configured unique constraints.
        bRepository.flush();

        // Create new Bs

Hope this helps!

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.