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注解防止无限递归,但未解决问题。
实际与预期行为
- 预期:查询用户信息时可查看其粉丝及关注列表。
- 实际:用户互相关注时应用崩溃抛出栈溢出;查询所有用户时能看到关注列表,但单个用户查询时无法获取关注列表。
问题分析
- Lombok @Data的equals/hashCode递归问题:
@Data自动生成的equals()和hashCode()会包含followings和followers集合,当两个用户互相关注时,A的followings包含B,B的followers包含A,调用equals时会无限递归调用对方集合的equals方法,导致栈溢出。 - @JsonIgnoreProperties配置错误:同时忽略
followers和followings会导致序列化时这两个字段都被隐藏,所以单个用户查询时无法获取关注/粉丝列表。 - 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
相关产品推荐
相关产品推荐

