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

Spring Data JPA @OneToMany映射保存时出现StackOverflowError求助

问题描述

我正在学习Spring Boot,有GYMS和HOURS两张数据表,在Gym实体中声明了@OneToMany映射,Hours实体中声明了@ManyToOne映射,但保存新健身房及其营业时间到数据库时出现StackOverflowError,求解决办法,谢谢!

数据表结构

CREATE TABLE gyms (
  id int NOT NULL AUTO_INCREMENT,
  name varchar(60) NOT NULL,
  address varchar(255),
  city varchar(255),
  province varchar(3),
  postal_code char (7),
  phone_number varchar(20),
  PRIMARY KEY (id)
);

CREATE TABLE hours (
  id int NOT NULL AUTO_INCREMENT,
  gym_id int NOT NULL,
  day_of_week INT,
  opening_time TIME,
  closing_time TIME,
  PRIMARY KEY (id),
  FOREIGN KEY (gym_id) REFERENCES gyms(id)
);

Gym实体类

@Entity
@Table (name = "gyms")
@NoArgsConstructor
@Getter
@Setter
public class Gym {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    private String address;
    private String city;
    private String province;
    private String postalCode;
    private String phoneNumber;

    @OneToMany(mappedBy = "gym", cascade = CascadeType.ALL)
    private List<Hours> hours;

    public Gym(String name, String address, String city, String province, String postalCode, String phoneNumber) {
        this.name = name;
        this.address = address;
        this.city = city;
        this.province = province;
        this.postalCode = postalCode;
        this.phoneNumber = phoneNumber;
    }
}

Hours实体类

@Entity
@Table (name = "hours")
@NoArgsConstructor
@Getter
@Setter
public class Hours {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "gym_id")
    private Gym gym;

    private int dayOfWeek;
    private Time openingTime;
    private Time closingTime;
    
    public Hours(int dayOfWeek, String openingTime, String closingTime) {
        this.dayOfWeek = dayOfWeek;
        this.openingTime = Time.valueOf(openingTime);
        this.closingTime = Time.valueOf(closingTime);
    }
}

GymService类

@Service
public class GymService {
    private final GymRepository gymRepository;
    private final HoursRepository hoursRepository;
    
    @Autowired
    public GymService(GymRepository gymRepository, HoursRepository hoursRepository) {
        this.gymRepository = gymRepository;
        this.hoursRepository = hoursRepository;
    }

    public Gym addNewGym(Gym gym, List<Hours> hours) {
        gym.setHours(hours);
        for (Hours hour : hours) {
            hour.setGym(gym);
        }
        return gymRepository.save(gym);
    }

}

问题原因及解决方案

出现StackOverflowError的核心原因是双向关联实体间的循环引用:Lombok默认生成的toString()(部分IDE或Lombok配置会自动添加)会包含关联字段,导致调用Gym.toString()时遍历hours列表,每个Hours.toString()又会调用关联的Gym.toString(),无限循环直到栈溢出。

解决步骤:

  1. 排除关联字段的toString生成
    在实体类上添加@ToString注解,明确排除互相引用的字段:

    // Gym类修改
    @Entity
    @Table(name = "gyms")
    @NoArgsConstructor
    @Getter
    @Setter
    @ToString(exclude = "hours")
    public class Gym {
        // 原有代码不变
    }
    
    // Hours类修改
    @Entity
    @Table(name = "hours")
    @NoArgsConstructor
    @Getter
    @Setter
    @ToString(exclude = "gym")
    public class Hours {
        // 原有代码不变
    }
    
  2. 同步处理equals和hashCode
    若Lombok自动生成了equals/hashCode方法,同样需要排除关联字段避免循环:

    // Gym类添加
    @EqualsAndHashCode(exclude = "hours")
    // Hours类添加
    @EqualsAndHashCode(exclude = "gym")
    
  3. 验证保存逻辑
    你的GymService保存逻辑是正确的:双向关联关系已正确建立,CascadeType.ALL能实现级联保存,无需修改。

额外建议

  • 双向关联中@OneToMany使用mappedBy是正确做法,避免双向维护外键导致数据不一致;
  • @ManyToOne上的FetchType.LAZY是默认值,显式声明有助于提升查询性能,避免不必要的关联数据加载。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 06:25:33