Unidirectional @OnetoMany mapping deletes all relationships and re-adds remaining ones rather than removing the specific one

╄→尐↘猪︶ㄣ 提交于 2021-01-28 01:56:22

问题


Given the following code

public class Course {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Review> reviews = new ArrayList<>();
}

public class Review {
    @Id
    @GeneratedValue
    private Long id;

    @Column(nullable = false)
    private String rating;
    private String description;
}

Saved course with 2 reviews.

If I try to remove one review from course.

course.getReviews().remove(0);

Hibernate fires following queries.

delete from course_reviews where course_id=? 
binding parameter [1] as [BIGINT] - [1]
insert into course_reviews (course_id, reviews_id) values (?, ?) 
binding parameter [1] as [BIGINT] - [1]
binding parameter [2] as [BIGINT] - [3]

Notice that it deletes all the relationships first and then inserts the remaining. Why this behavior? Why couldn't it be more specific and delete just that one record storing the relationship.


回答1:


Not sure if this is due to bag semantics(because you use a List rather than Set for reviews) or just because Hibernate sometimes does so called "collection recreations". Try using a Set.




回答2:


Hibernate does that because it has no idea about how the entities are related. Since there is no information about how relations are identified, it uses the only information it has - objects in the memory. So it clears the table by the predicate and persists the entities from memory.

You need to use @JoinColumn on the child side and mappedBy parameter of @OneToMany on the parent side.




回答3:


First of all the behavior that you see is described in the documentation:

The unidirectional associations are not very efficient when it comes to removing child entities. In the example above, upon flushing the persistence context, Hibernate deletes all database rows from the link table (e.g. Person_Phone) that are associated with the parent Person entity and reinserts the ones that are still found in the @OneToMany collection.

On the other hand, a bidirectional @OneToMany association is much more efficient because the child entity controls the association.

As for the question:

Why this behavior? Why couldn't it be more specific and delete just that one record storing the relationship.

The answer is not so simple and require deep diving into the hibernate source code.

The key point of the entity's collection processing in hibernate is the PersistentCollection interface. As it stated in the comments to this interface:

Hibernate wraps a java collection in an instance of PersistentCollection. This mechanism is designed to support tracking of changes to the collection's persistent state and lazy instantiation of collection elements. The downside is that only certain abstract collection types are supported and any extra semantics are lost.

The important place in our discussion have the following method of this interface:

/**
  * Do we need to completely recreate this collection when it changes?
  *
  * @param persister The collection persister
  * @return {@code true} if a change requires a recreate.
  */
boolean needsRecreate(CollectionPersister persister);

Hibernate creates an action queue for scheduling creates/removes/updates at flushing time (see the AbstractFlushingEventListener.flushCollections method). So, our collection belongs to one of the CollectionUpdateAction action in this queue.

As you can see from the CollectionUpdateAction.execute() method implementation, hibernate checks need of a collection recreation based on the on the collection.needsRecreate(persister) call.

The PersistentCollection interface has the following hierarchy of implementations:

PersistentCollection
   |
   |-- AbstractPersistentCollection
           |
           |-- PersistentArrayHolder
           |-- PersistentBag
           |-- PersistentIdentifierBag
           |-- PersistentList
           |-- PersistentMap
                  |
                  |-- PersistentSortedMap
           |
           |-- PersistentSet
                  |
                  |-- PersistentSortedSet

Actually, the needsRecreate method implemented only in the AbstractPersistentCollection and overridden for the PersistentBag in the following way:

@Override
public boolean needsRecreate(CollectionPersister persister) {
    return !persister.isOneToMany();
}

Hibernate decides to what type from the above hierarchy a collection belongs at time of parsing your domain model.

  1. When you use the described in your question mapping:
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Review> reviews;

hibernate will treat it as PersistentBag and the method PersistentCollection.needsRecreate returns true (because the BasicCollectionPersister is used).

  1. You can use the @OrderColumn annotation:
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@OrderColumn
private List<Review> reviews;

in this case the collection will be treated as the PersistentList and you will avoid the collection recreation. But this is also required additional order column (must be of integral type) in the Course_Review table. And when you will try to remove an item from the beginning of the list you will have also a lot of the order columns updates.

  1. You can use the Set interface instead of List (as was noticed by Christian Beikov):
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Review> reviews;

in this case the collection will be treated as the PersistentSet and you will avoid the collection recreation as well. When using Sets, it’s very important to supply proper equals/hashCode implementations for child entities. A better equals/hashCode implementation, making use of a natural-id or business-key. And you will be able to remove an item from this collection only by the object reference as the method remove(int index) just absent in the Set interface.



来源:https://stackoverflow.com/questions/64363313/unidirectional-onetomany-mapping-deletes-all-relationships-and-re-adds-remainin

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!