Hibernate doesn't remove entities from the database if you change their identifiers. It just adds the new ones

  • Wildfly 21
  • Hibernate 5.3.18.Final (JPA provider)

I stumbled in a very strange and nonintuitive behaviour in Hibernate.

PhoneId

    @Embeddable
    public class PhoneId
    {
        @Column(name = "PERSON_ID")
        private int personId;

        @Column(name = "PHONE_ID")
        private int phoneId;

        // Getters, setters...
    }

Phone

    @Entity
    public class Phone
    {
        @EmbeddedId
        private PhoneId id;
 
        private String number;

        @ManyToOne
        @JoinColumn(name = "person_id")
        private Person person;

        // Getters, setters...
    }

Person

    @Entity
    public class Person
    {
        @Id
        private int id;
    
        @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
        private List<Phone> phones = new ArrayList<>();

        // Getters, setters...
    }

Now, suppose that I have these phones in the database associated with person with id 1:

  • PERSON_ID=1, PHONE_ID=1, NUMBER=phone1
  • PERSON_ID=1, PHONE_ID=2, NUMBER=phone2

The I get a person:

    Person person = em.find(Person.class, 1);
    person.getPhones().size() // 2

and, working with it in detached mode, I change the two phone ids to 3 and 4, respectively. I also add a third phone with id 5 to this person. After that, I want to update the database with these changes with:

    person.getPhones().size() // 3
    Person newPerson = em.merge(person);

So, the problem is this:

After that, I would expect to see in the phones table in the database these records:

  • PERSON_ID=1, PHONE_ID=3, NUMBER=phone1
  • PERSON_ID=1, PHONE_ID=4, NUMBER=phone2
  • PERSON_ID=1, PHONE_ID=5, NUMBER=phone3

But do you know what I see? I actually see this:

  • PERSON_ID=1, PHONE_ID=1, NUMBER=phone1
  • PERSON_ID=1, PHONE_ID=2, NUMBER=phone2
  • PERSON_ID=1, PHONE_ID=3, NUMBER=phone1
  • PERSON_ID=1, PHONE_ID=4, NUMBER=phone2
  • PERSON_ID=1, PHONE_ID=5, NUMBER=phone3

So, when I saved the changes to the database I had only 3 phones in the collection and Hibernate didn’t delete the phones that were in the database (their ids were no longer in the phone collection) and added to them in the database the phones with the new ids. What’s happening here? What’s the use of the orphanRemoval attribute if it doesn’t remove the childs with ids that were no longer in the phones collection?

If I have only 3 phones in a collection I want to have only 3 phones in the database as well. Very fair. Why is Hibernate ignoring the phones that are already in the database and not deleting them? After all, the phones in the database have different ids than those in the collection.

This is completely nonintuitive and wrong in my opinion, but if I’m missing something or doing something wrong, I would like very much to know.

Cross-posted: java - Hibernate doesn't remove entities from the database if you change their identifiers. It just adds the new ones - Stack Overflow

I think the issue is that you have a bidirectional mapping and that the owner of the association is on the many-to-one side. Can you try mapping it like this instead:

@Entity
public class Phone
{
    @EmbeddedId
    private PhoneId id;

    private String number;

    // Getters, setters...
}

@Entity
public class Person
{
    @Id
    private int id;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "person_id")
    private List<Phone> phones = new ArrayList<>();

    // Getters, setters...
}

But the foreign key (join column) is on the Phone table in the database, not on the Person table. Can we do this?

Sure, that’s what the mapping I presented you should do.