尝试在 Spring 中填充联接 table 时出现 StackOverflowError?

StackOverflowError when trying to fill join table in Spring?

我在尝试填充连接时收到 WhosebugError table...请参阅下面的代码。

我有两个实体:

@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
}

当我尝试按如下方式填充联接 table 时:

user.setSubscribedAppointments(appointment); //sheikh fuad
appointment.setSubscribers(user);
        
appointmentRepository.save(appointment);
userRepository.save(user);

我收到 WhosebugError:

java.lang.WhosebugError: 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$HibernateProxyIT2B41W.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]
...

然后这三行重复很多次:

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]

为用户设置约会是否正确,反之亦然?或者如何填充由 spring?

生成的连接 table

按此顺序尝试..它可能会帮助您保存记录。

appointment.setSubscribers(user);
appointmentRepository.save(appointment);
user.setSubscribedAppointments(appointment); //sheikh fuad
userRepository.save(user);

根据评论,您正在使用 Lombok 的注释 @EqualsAndHashCode

如果您检查生成的代码,您会看到 User 正在调用 Setof Appointments(可能由 HashSet 支持)哈希码。 Appointment 调用用户哈希码的 Set 也是如此。

Lombok 生成如下内容:

  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;
  }

如果您看到 HashSet 哈希码的 JDK 源代码:

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;
}

正在迭代集合以计算每个成员哈希码。

既然你这样做了:

user.setSubscribedAppointments(appointment); //sheikh fuad
appointment.setSubscribers(user);

在两个 setXXX 中都触发了对 hashcode 的调用。

Whosebug 可能出现在第二行,因为 user as appointment 和 appointment 具有相同的用户。在计算hashcode的时候,一个一遍又一遍的调用另一个。

你需要在哈希码计算中打破这种依赖关系(可能同样发生在 equals 方法中)。您需要删除或配置 @EqualsAndHashCode,因此它确实会在某些时候中断计算。如果您自己实现哈希码,那么更简单的解决方案是仅使用主键(如果实体是持久化的)进行计算。如果实体没有持久化,那么你可以使用一些其他字段,但要注意计算中可能发生的循环(你必须avoid/break它)。

实体约会依赖于 user_id,当您要保存约会时它为空。所以首先保存用户而不在其中设置约会。然后设置用户预约并保存。最后,为用户设置约会并再次保存。这意味着您必须具有以下几行:

userRepository.save(user);
appointment.setSubscribers(user);
appointmentRepository.save(appointment);
user.setSubscribedAppointments(appointment); 
userRepository.save(user);