Issue
I get StackOverflowError when tyring to fill join table...see Code below.
I have two Entites:
@Entity
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long userId;
@ManyToMany
@JoinTable(
name = "user_appointment",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "appointment_id"))
Set<Appointment> subscribedAppointments;
}
@Entity
public class Appointment {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long appointmentId;
@JsonIgnore
@ManyToMany(mappedBy = "subscribedAppointments")
Set<User> subscribers; //users who added this appointment to their calendar
}
When I try to fill the join table as following:
user.setSubscribedAppointments(appointment); //sheikh fuad
appointment.setSubscribers(user);
appointmentRepository.save(appointment);
userRepository.save(user);
I get StackoverflowError:
java.lang.StackOverflowError: null
at java.base/java.lang.Exception.<init>(Exception.java:102) ~[na:na]
at java.base/java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:89) ~[na:na]
at java.base/java.lang.reflect.InvocationTargetException.<init>(InvocationTargetException.java:73) ~[na:na]
at jdk.internal.reflect.GeneratedMethodAccessor60.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:56) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
at com.taqwaapps.entity.Country$HibernateProxy$pGwXHWph.hashCode(Unknown Source) ~[classes/:na]
at com.taqwaapps.entity.City.hashCode(City.java:25) ~[classes/:na]
at jdk.internal.reflect.GeneratedMethodAccessor59.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:56) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
at com.taqwaapps.entity.City$HibernateProxy$9IT2B41W.hashCode(Unknown Source) ~[classes/:na]
at com.taqwaapps.entity.District.hashCode(District.java:21) ~[classes/:na]
at com.taqwaapps.entity.Appointment.hashCode(Appointment.java:31) ~[classes/:na]
at java.base/java.util.ImmutableCollections$Set12.hashCode(ImmutableCollections.java:520) ~[na:na]
at com.taqwaapps.entity.User.hashCode(User.java:29) ~[classes/:na]
at com.taqwaapps.entity.Appointment.hashCode(Appointment.java:31) ~[classes/:na]
at java.base/java.util.ImmutableCollections$Set12.hashCode(ImmutableCollections.java:520) ~[na:na]
...
Then these three lines are repeated many many times:
at com.taqwaapps.entity.User.hashCode(User.java:29) ~[classes/:na]
at com.taqwaapps.entity.Appointment.hashCode(Appointment.java:31) ~[classes/:na]
at java.base/java.util.ImmutableCollections$Set12.hashCode(ImmutableCollections.java:520) ~[na:na]
Is it correct to set the appointments for the user and vice versa? Or how to fill the join table which was generated by spring?
Solution
From the comments, you are using Lombok's annotation @EqualsAndHashCode
.
If you check the generated code, you will see that User is calling the Set
of Appointments (probably backed by HashSet
) hashcode. The same is true for Appointment that is calling Set of Users hashcode.
Lombok generates something like this:
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $set = this.getSet();
result = result * PRIME + ($set == null ? 43 : $set.hashCode());
return result;
}
If you see the JDK source code for HashSet hashcode:
public int hashCode() {
int h = 0;
Iterator<E> i = iterator();
while (i.hasNext()) {
E obj = i.next();
if (obj != null)
h += obj.hashCode();
}
return h;
}
The set is being iterated for calculating each member hashcode.
Since you are doing this:
user.setSubscribedAppointments(appointment); //sheikh fuad
appointment.setSubscribers(user);
A call to hashcode is triggered in both setXXX.
The stackoverflow is probably occurring in the second line, since user as appointment and appointment has the same user. When calculating the hashcode, one calls the other over and over again.
You need to break this dependency in hashcode calculation (probably the same occurs in equals method). You need to remove, or configure the @EqualsAndHashCode, so it does break at some point the calculation. If you implement hashcode by yourself, then the easier solution is to calculate using only the primary key (if the entity is persisted) for example. If the entity is not persisted, then you can use some other fields, but taking into attention the circularity that can occur in the calculation (you have to avoid/break it).
Answered By - pringi
Answer Checked By - Clifford M. (JavaFixing Volunteer)