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
@OneToMany
association, Set
is preferred.It should be clear that indexed
Collections
andSets
allow the most efficient operations in terms of adding, removing and updating elements.
@OneToMany
relationship (@ManyToOne
managing), List
and Bags
are efficient.
Bags
andLists
are the most efficient inverseCollections
.
Having said that, which is more preferable:
A Set
over a List
in a unidirectional @OneToMany
mapping?
Or, do I have to tweak my domain model by adding a bidirectional relationship to use a List
, especially when there are duplicates?
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:
List
/ Collection
+ @OneToMany
-> One Element Added: 1 delete, N inserts , One Element Removed: 1 delete, N insertsList
+ @OneToMany
+ @IndexColumn
/ @OrderColumn
-> One Element Added: 1 insert, M updates, One Element Removed: 1 delete, M updatesSet
+ @OneToMany
-> One Element Added: 1 insert , One Element Removed: 1 deleteFor 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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With