ChatGPT解决这个技术问题 Extra ChatGPT

How does JPA orphanRemoval=true differ from the ON DELETE CASCADE DML clause

I am a little confused about the JPA 2.0 orphanRemoval attribute.

I think I can see it is needed when I use my JPA provider's DB generation tools to create the underlying database DDL to have an ON DELETE CASCADE on the particular relation.

However, if the DB exists and it already has an ON DELETE CASCADE on the relation, is this not enough to cascade the deletion appropriately? What does the orphanRemoval do in addition?

Cheers


M
Martin Konecny

orphanRemoval has nothing to do with ON DELETE CASCADE.

orphanRemoval is an entirely ORM-specific thing. It marks "child" entity to be removed when it's no longer referenced from the "parent" entity, e.g. when you remove the child entity from the corresponding collection of the parent entity.

ON DELETE CASCADE is a database-specific thing, it deletes the "child" row in the database when the "parent" row is deleted.


Does this mean that they have the safe effect, but a different system is responsible for making it happen?
Anon, it does not have the same effect. ON DELETE CASCADE tells the DB to delete all child records when the parent is deleted. That is if I delete the INVOICE, then delete all of the ITEMS on that INVOICE. OrphanRemoval tells the ORM that if I remove an Item object from the collection of Items that belong to an Invoice object (in memory operation), and then "save" the Invoice, the removed Item should be deleted from the underlying DB.
If you use uni-directional relationship, then the orphan will be removed automatically even though you don't set orphanRemoval=true
T
Tiny

An example taken form here:

When an Employee entity object is removed, the remove operation is cascaded to the referenced Address entity object. In this regard, orphanRemoval=true and cascade=CascadeType.REMOVE are identical, and if orphanRemoval=true is specified, CascadeType.REMOVE is redundant.

The difference between the two settings is in the response to disconnecting a relationship. For example, such as when setting the address field to null or to another Address object.

If orphanRemoval=true is specified the disconnected Address instance is automatically removed. This is useful for cleaning up dependent objects (e.g. Address) that should not exist without a reference from an owner object (e.g. Employee).

If only cascade=CascadeType.REMOVE is specified, no automatic action is taken since disconnecting a relationship is not a remove operation.

To avoid dangling references as a result of orphan removal, this feature should only be enabled for fields that hold private non shared dependent objects.

I hope this makes it more clear.


After reading your answer, I realize exact difference between both of them and my issue has been resolved. I got stuck in deleting the child entities from database, if those are disconnected(removed) from defined collection in parent entity. For same I asked the question 'stackoverflow.com/questions/15526440/…'. Just adding my comment to link both questions.
@forhas please go through question stackoverflow.com/questions/58185249/…
O
Onur

The moment you remove a child entity from the collection you will also be removing that child entity from the DB as well. orphanRemoval also implies that you cannot change parents; if there's a department that has employees, once you remove that employee to put it in another deparment, you will have inadvertantly removed that employee from the DB at flush/commit(whichver comes first). The morale is to set orphanRemoval to true so long as you are certain that children of that parent will not migrate to a different parent throughout their existence. Turning on orphanRemoval also automatically adds REMOVE to cascade list.


Exactly correct...also called a "private" parent/child relationship.
That means as soon as i call department.remove(emp); that employee will be deleted from the emp table without even calling commit()
V
Vlad Mihalcea

Entity state transitions

JPA translates entity state transitions to SQL statements, like INSERT, UPDATE or DELETE.

https://i.stack.imgur.com/Hez6p.png

When you persist an entity, you are scheduling the INSERT statement to be executed when the EntityManager is flushed, either automatically or manually.

when you remove an entity, you are scheduling the DELETE statement, which will be executed when the Persistence Context is flushed.

Cascading entity state transitions

For convenience, JPA allows you to propagate entity state transitions from parent entities to child one.

So, if you have a parent Post entity that has a @OneToMany association with the PostComment child entity:

https://i.stack.imgur.com/OIJg4.png

The comments collection in the Post entity is mapped as follows:

@OneToMany(
    mappedBy = "post", 
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<Comment> comments = new ArrayList<>();

CascadeType.ALL

The cascade attribute tells the JPA provider to pass the entity state transition from the parent Post entity to all PostComment entities contained in the comments collection.

So, if you remove the Post entity:

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

entityManager.remove(post);

The JPA provider is going to remove the PostComment entity first, and when all child entities are deleted, it will delete the Post entity as well:

DELETE FROM post_comment WHERE id = 1
DELETE FROM post_comment WHERE id = 2

DELETE FROM post WHERE id = 1

Orphan removal

When you set the orphanRemoval attribute to true, the JPA provider is going to schedule a remove operation when the child entity is removed from the collection.

So, in our case,

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

PostComment postComment = post.getComments().get(0);
assertEquals(1L, postComment.getId());

post.getComments().remove(postComment);

The JPA provider is going to remove the associated post_comment record since the PostComment entity is no longer referenced in the comments collection:

DELETE FROM post_comment WHERE id = 1

ON DELETE CASCADE

The ON DELETE CASCADE is defined at the FK level:

ALTER TABLE post_comment 
ADD CONSTRAINT fk_post_comment_post_id 
FOREIGN KEY (post_id) REFERENCES post 
ON DELETE CASCADE;

Once you do that, if you delete a post row:

DELETE FROM post WHERE id = 1

All the associated post_comment entities are removed automatically by the database engine. However, this can be a very dangerous operation if you delete a root entity by mistake.

Conclusion

The advantage of the JPA cascade and orphanRemoval options is that you can also benefit from optimistic locking to prevent lost updates.

If you use the JPA cascading mechanism, you don't need to use DDL-level ON DELETE CASCADE, which can be a very dangerous operation if you remove a root entity that has many child entities on multiple levels.


So in Orphan Removal part of your answer: post.getComments().remove(postComment); will work in OneToMany bidirectional mapping only because of Persist cascade. Without cascading and not having removal on ManyToOne side, like in your example, removal of connection between 2 entities would not be persisted in DB?
Orphan removal is not affected by CascadeType. It's a complementary mechanism. Now, you are mistaking removal with persisting. Orphan removal is about deleting unreferenced associations while persisting is about saving new entities. You need to follow the links provided in the answer to get a better understanding of these concepts.
I don't understand one thing: how will orphan removal kick in in bidirectional mapping if we never remove connection on M side? I think that removing PostComment from Post's list without setting PostComment.post to null will not result in removal of connection between those 2 entities in DB. That is why I think orphan removal won't kick in, in relational world there PostComment is not orphan. I will test it when I get some free time.
I added these two examples in my High-Performance Java Persistence GitHub repository which demonstrate how it all works. You don't need to synchronize the child side as you usually have to do for removing entities directly. However, orphan removal only works if cascading is added, but that seems to be a Hibernate limitation, not a JPA specification.
H
Heri

The equivalent JPA mapping for the DDL ON DELETE CASCADE is cascade=CascadeType.REMOVE. Orphan removal means that dependent entities are removed when the relationship to their "parent" entity is destroyed. For example if a child is removed from a @OneToMany relationship without explicitely removing it in the entity manager.


cascade=CascadeType.REMOVE is NOT equivalent of ON DELETE CASCADE. On do remove in application code and doesn't affect at DDL, other executed in DB. See stackoverflow.com/a/19696859/548473
u
user3572554

The difference is: - orphanRemoval = true: "Child" entity is removed when it's no longer referenced (its parent may not be removed). - CascadeType.REMOVE: "Child" entity is removed only when its "Parent" is removed.


p
pzeszko

@GaryK answer is absolutely great, I've spent an hour looking for an explanation orphanRemoval = true vs CascadeType.REMOVE and it helped me understand.

Summing up: orphanRemoval = true works identical as CascadeType.REMOVE ONLY IF we deleting object (entityManager.delete(object)) and we want the childs objects to be removed as well.

In completely different sitiuation, when we fetching some data like List<Child> childs = object.getChilds() and then remove a child (entityManager.remove(childs.get(0)) using orphanRemoval=true will cause that entity corresponding to childs.get(0) will be deleted from database.


You have a typo in your second paragraph: There is no such method as entityManager.delete(obj); it's entityManager.remove(obj).
k
kunal

orphan removal has the same effect as ON DELETE CASCADE in the following scenario:- Lets say we have a simple many to one relationship between student entity and a guide entity, where many students can be mapped to the same guide and in database we have a foreign key relation between Student and Guide table such that student table has id_guide as FK.

    @Entity
    @Table(name = "student", catalog = "helloworld")
    public class Student implements java.io.Serializable {
     @Id
     @GeneratedValue(strategy = IDENTITY)
     @Column(name = "id")
     private Integer id;

    @ManyToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
    @JoinColumn(name = "id_guide")
    private Guide guide;

// The parent entity

    @Entity
    @Table(name = "guide", catalog = "helloworld")
    public class Guide implements java.io.Serializable {

/**
 * 
 */
private static final long serialVersionUID = 9017118664546491038L;

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;

@Column(name = "name", length = 45)
private String name;

@Column(name = "salary", length = 45)
private String salary;


 @OneToMany(mappedBy = "guide", orphanRemoval=true) 
 private Set<Student> students = new  HashSet<Student>(0);

In this scenario, the relationship is such that student entity is the owner of the relationship and as such we need to save the student entity in order to persist the whole object graph e.g.

    Guide guide = new Guide("John", "$1500");
    Student s1 = new Student(guide, "Roy","ECE");
    Student s2 = new Student(guide, "Nick", "ECE");
    em.persist(s1);
    em.persist(s2);

Here we are mapping the same guide with two different student objects and since the CASCADE.PERSIST is used , the object graph will be saved as below in the database table(MySql in my case)

STUDENT table:-

ID Name Dept Id_Guide

1 Roy ECE 1

2 Nick ECE 1

GUIDE Table:-

ID NAME Salary

1 John $1500

and Now if I want to remove one of the students, using

      Student student1 = em.find(Student.class,1);
      em.remove(student1);

and when a student record is removed the corresponding guide record should also be removed, that's where CASCADE.REMOVE attribute in the Student entity comes into picture and what it does is ;it removes the student with identifier 1 as well the corresponding guide object(identifier 1). But in this example, there is one more student object which is mapped to the same guide record and unless we use the orphanRemoval=true attribute in the Guide Entity , the remove code above will not work.