4
votes

I've been reading posts about orphanRemoval= true in JPA . According to documentation :

orphanRemoval is a flag -

Whether to apply the remove operation to entities that have been removed from the relationship and to cascade the remove operation to those entities.

Also I refered to this article for more info , where they have tried to set child entity (address - in their example ) as null.

I currently understand that making orphanRemoval= true will perform similar operation as cascade=CascadeType.REMOVE and if I remove my parent entity , it will delete the child entity as well .

What i want to test is the additional functionality that it brings which is removal of entities that are not referenced by their parent entity.

I am trying to create a similar scenario where I am setting the new collection of phones as new ArrayList<>() where the parent entity is Person .

Following are my entity classes .

Person.java

@Entity
@Table(name = "person")
@Data
public class Person {

    @Id
    int pd ;
    String fname;
    String lname;

    @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="person",orphanRemoval=true)
    List<Phone> phones = new ArrayList<>() ;

    public boolean addPhone(Phone phone) {
        boolean added = this.phones.add(phone);
        phone.setPerson(this);
        return added;
    }
}

Phone.java

@Entity
@Table(name = "phone")
@Data
public class Phone {
    private int countryCode;
    @Id
    private String number ;

    @ManyToOne
    @JoinColumn(name="fk_person")
    Person person ;

}

main class

public void testFlow() {

    Person p = fetchById(765);      
    p.setPhones(new ArrayList<>());
    personRepo.save(p); **// exception on this line**
    getPersons();
}


public Person fetchById(int id) {

    Optional<Person> pe = personRepo.findById(id);
    Person person = pe.get();
    System.out.println("person is :"+ person.getFname());
    System.out.println("cc is :"+ person.getPhones().get(0).getNumber());   

    return person; 

}

public List<Person> getPersons() {

        List<Person> persons = personRepo.findAll();
        persons.forEach(p -> {
            System.out.println("person :"+p.getPd());
            System.out.println("person phones :"+p.getPhones().get(0).getNumber());
            System.out.println("=================================");
        });
        return persons;
}

The entry method is testFlow() .

When I execute this code , I get error :

org.hibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: com.example.entity.Person.phones

Any clue how i can test the working example of orphanRemoval ?

2
Where is the exception thrown?Andronicus
in the testFlow() , call to savedevcodes

2 Answers

4
votes

The problem is caused by the following line:

p.setPhones(new ArrayList<>());

In Hibernate, you cannot overwrite a collection retrieved from the persistence context if the association has orphanRemoval = true specified. If your goal is to end up with an empty collection, use p.getPhones().clear() instead.

2
votes

This is the line the exception should be thrown:

personRepo.save(p);

It happens, because you are trying to save Person that doesn't reference any Phones. I means, that you're dereferencing only Person but not the Phone entities. Since it's a bidirectional relationship, you would have to dereference both:

public void testFlow() {
    Person p = fetchById(765);      
    p.getPhones().foreach(ph -> ph.setPerson(null));
    p.setPhones(new ArrayList<>());
    personRepo.save(p); **// exception on this line**
    getPersons();
}