shazinltc
shazinltc

Reputation: 3666

JPA OneToMany : List vs Set

I have two entities: UserAccount and Notification. These have a relationship as shown below.

 public class UserAccount {

    @Id
    @Column(name = "USER_NAME")
    private String emailId;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(name = "USERS_NOTIFICATIONS", joinColumns = { @JoinColumn(name = "USER_NAME") }, inverseJoinColumns = { @JoinColumn(name = "NOTIFICATION_ID") })
    private List<Notification> notifications;

    //setters, getter, equals and hashcode
 }

Both equals() and hashcode() are overridden (generated by the IDE with business key/primary key).

Given a UserAccount, when I add the first Notification, it results in an INSERT statement. But on further addition for the same UserAccount, it first deletes and then inserts:

Hibernate: delete from USERS_NOTIFICATIONS where USER_NAME=?
Hibernate: insert into USERS_NOTIFICATIONS (USER_NAME, NOTIFICATION_ID) values (?, ?)
Hibernate: insert into USERS_NOTIFICATIONS (USER_NAME, NOTIFICATION_ID) values (?, ?)
Hibernate: insert into USERS_NOTIFICATIONS (USER_NAME, NOTIFICATION_ID) values (?, ?)
//as many inserts as the notifications the user has

The same happens with every UserAccount. If I replace the List with Set, a normal INSERT occurs. I found the reason after reading this documentation and a blog.

Observations from docs

It should be clear that indexed Collections and Sets allow the most efficient operations in terms of adding, removing and updating elements.

Bags and Lists are the most efficient inverse Collections.


Having said that, which is more preferable:

  1. A Set over a List in a unidirectional @OneToMany mapping?

  2. Or, do I have to tweak my domain model by adding a bidirectional relationship to use a List, especially when there are duplicates?

Upvotes: 32

Views: 33183

Answers (3)

Matthias Berg
Matthias Berg

Reputation: 382

I use a Set when I do not need ordering. This enables also set-operations. On the other hand, I use a List if ordering is required.

The semantic of the container is in my eyes the relevant aspect to decide which one to use.

Upvotes: 0

kwisatz
kwisatz

Reputation: 1296

I faced this problem not so long ago...

I found this article: Performance Antipatterns of One To Many Association in Hibernate https://fedcsis.org/proceedings/2013/pliks/322.pdf

in short:

  • Bag semantics -> List / Collection + @OneToMany -> One Element Added: 1 delete, N inserts , One Element Removed: 1 delete, N inserts
  • List semantics -> List + @OneToMany + @IndexColumn / @OrderColumn -> One Element Added: 1 insert, M updates, One Element Removed: 1 delete, M updates
  • Set semantics -> Set + @OneToMany -> One Element Added: 1 insert , One Element Removed: 1 delete

For me: yes that mean that you have to change your List to Set for unidirectional @OneToMany. So I changed my model to match with Hibernate expectations and that cause a lot of issues because the view part of the application was relying on List mostly...

In one hand the Set is a logical choice for me because there are no duplications, in the other hand List were easier to deal with.

So JPA/Hibernate forced me to change the model object and that was not the first time, when you are using @EmbededId you do something that you probably won't do in the same way without JPA/Hibernate. And when you have to be aware of HibernateProxy in all the application especially in equals methods ... else if(object instanceof HibernateProxy) { ..., you notice the JPA/Hibernate persitence layer is a little bit intrusive in others layers.

But when I use directely JDBC I also use to change the model or the buisness methods to facilitate the persistence... Layers isolation is may be a dream or cost too much to be done at 100%?

And you can order a Set if they are SortedSet like TreeSet with the annotation @OrderBy

That bring a problem when some code rely on List and cannot be changed (such as JSF/PrimeFaces <dataTable> or <repeat> components) So you have to change your Set into List and go back to Set but if you do setNotifications(new HashSet<>(notificationList)) you will have extra queries because the set is a org.hibernate.collection.PersistentSet managed by Hibernate... So I used addAll() and removeAll() instead of setters:

protected <E> void updateCollection(@NonNull Collection<E> oldCollection, @NonNull Collection<E> newCollection) {
    Collection<E> toAdd = new ArrayList<>(newCollection) ;
    toAdd.removeAll(oldCollection) ;

    Collection<E> toRemove = new ArrayList<>(oldCollection) ;
    toRemove.removeAll(newCollection) ;

    oldCollection.removeAll(toRemove) ;
    oldCollection.addAll(toAdd) ;
}

Mind the equals() and hashCode() methods of your @Entity...

One other problem is that you need to Master both JPA and Hibernate if you want to use JPA with Hibernate as the implementation because the Set/List/Bag semantic is from Hibernate not from JPA (correct me if I'm wrong)

A specification is made for abstracting the implementation to not depend on one specific vendor. Although most of JavaEE specs succed, JPA failed for me and I gave up to be independent of Hibernate

Upvotes: 23

deepakraut
deepakraut

Reputation: 953

List: Allows duplicate elements in it.

Set: All elements should be unique.

Now, delete may be happening because you are over-writing element in list, and so when you modify persisted entity of UserAccount type, it is removing the entity which is in list previously.

Upvotes: 1

Related Questions