You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中双向关注触发java.lang.StackOverflowError问题求助

用户互相关注功能栈溢出问题解决

问题背景

开发基于Spring Boot的Java应用,使用UserEntity实现用户互相关注功能,但两个用户互相关注时抛出java.lang.StackOverflowError,且按用户名查询单个用户时无法获取其关注列表。

现有实体类代码

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "user")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String userName;
    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "user_followings",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "following_id")
    )
    @JsonIgnoreProperties({"followers", "followings"})
    @Builder.Default
    private Set<UserEntity> followings = new HashSet<>();

    @ManyToMany(mappedBy = "followings", fetch = FetchType.EAGER)
    @JsonIgnoreProperties({"followers", "followings"})
    @Builder.Default
    private Set<UserEntity> followers = new HashSet<>();
}

已尝试方案

  • 在followings和followers字段上使用@JsonIgnoreProperties注解防止无限递归,但未解决问题。

实际与预期行为

  • 预期:查询用户信息时可查看其粉丝及关注列表。
  • 实际:用户互相关注时应用崩溃抛出栈溢出;查询所有用户时能看到关注列表,但单个用户查询时无法获取关注列表。

问题分析

  1. Lombok @Data的equals/hashCode递归问题:@Data自动生成的equals()和hashCode()会包含followings和followers集合,当两个用户互相关注时,A的followings包含B,B的followers包含A,调用equals时会无限递归调用对方集合的equals方法,导致栈溢出。
  2. @JsonIgnoreProperties配置错误:同时忽略followers和followings会导致序列化时这两个字段都被隐藏,所以单个用户查询时无法获取关注/粉丝列表。
  3. FetchType.EAGER的加载压力:立即加载会导致每次查询用户都递归加载所有关联的关注/粉丝对象,加剧栈溢出风险,同时影响性能。

解决方案

1. 修正Lombok的equals/hashCode生成规则

使用@EqualsAndHashCode注解排除关联集合字段,避免递归调用,同时调整@JsonIgnoreProperties和加载策略:

@Entity
@Table(name = "user")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(exclude = {"followings", "followers"}) // 排除关联集合,避免equals递归
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String userName;
    private String password;

    @ManyToMany(fetch = FetchType.LAZY) // 改为懒加载,降低初始化压力
    @JoinTable(
            name = "user_followings",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "following_id")
    )
    @JsonIgnoreProperties("followers") // 只忽略粉丝列表,避免序列化递归
    @Builder.Default
    private Set<UserEntity> followings = new HashSet<>();

    @ManyToMany(mappedBy = "followings", fetch = FetchType.LAZY) // 改为懒加载
    @JsonIgnoreProperties("followings") // 只忽略关注列表,避免序列化递归
    @Builder.Default
    private Set<UserEntity> followers = new HashSet<>();
}

2. 正确维护双向关联的业务逻辑

在添加关注的业务方法中,必须同时维护双向关联,且确保事务覆盖整个操作:

@Service
@Transactional
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void followUser(Long userId, Long followingId) {
        UserEntity user = userRepository.findById(userId)
                .orElseThrow(() -> new RuntimeException("用户不存在"));
        UserEntity followingUser = userRepository.findById(followingId)
                .orElseThrow(() -> new RuntimeException("关注用户不存在"));
        
        // 避免重复添加
        if (!user.getFollowings().contains(followingUser)) {
            user.getFollowings().add(followingUser);
            followingUser.getFollowers().add(user);
            userRepository.save(user);
        }
    }
}

3. 调整查询逻辑(适配懒加载)

如果使用FetchType.LAZY,查询单个用户时需要通过JOIN FETCH显式初始化关联集合:

@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
    @Query("SELECT u FROM UserEntity u LEFT JOIN FETCH u.followings LEFT JOIN FETCH u.followers WHERE u.userName = :userName")
    Optional<UserEntity> findByUserNameWithFollowingsAndFollowers(@Param("userName") String userName);
}

4. 禁用toString递归(可选)

如果toString()也触发递归问题,可添加注解排除关联集合:

@Entity
@Table(name = "user")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(exclude = {"followings", "followers"})
@ToString(exclude = {"followings", "followers"}) // 排除关联集合避免toString递归
public class UserEntity {
    // ... 原有字段
}

内容的提问来源于stack exchange,提问作者ssvt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 17:44:59