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

JPA双向映射设置时出现StackOverflowError问题求助

双向关联导致StackOverflowError或关联字段为null的问题解决

问题背景

存在三个实体类:Theatre、Screen、SeatCategory,两两之间为一对多双向映射。通过包含三类信息的DTO创建Theatre实例时,出现两个矛盾问题:

  • 若设置Screen关联的Theatre、SeatCategory关联的Screen,会抛出StackOverflowError
  • 若不设置这些关联,数据库中Screen的theatre_id、SeatCategory的screen_id字段为null,且接口响应中关联字段也为null,无法建立预期的双向关系

实体类代码

Theatre实体

@Entity
@Table(name = "theatres")
public class Theatre {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private String theatreId;
    private String name;
    
    // 修改FetchType为LAZY,添加@JsonManagedReference解决序列化循环
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "theatre")
    @JsonManagedReference
    Set<Screen> screens = new HashSet<>();
    
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "theatre")
    Set<Show> shows = new HashSet<>();
    
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "theatre")
    Set<Movie> movies = new HashSet<>();
    
    @ManyToOne
    @JoinColumn(name = "location_id")
    Location location;
    
    @JsonIgnore
    @ManyToOne
    @JoinColumn(name="user_id")
    User user;

    // 添加辅助方法维护双向关联
    public void addScreen(Screen screen) {
        screens.add(screen);
        screen.setTheatre(this);
    }
}

Screen实体

@Entity
@Table(name = "screens")
public class Screen {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private String screenId;
    private String name;
    
    // 添加@JsonBackReference解决序列化循环
    @ManyToOne
    @JoinColumn(name = "theatre_id")
    @JsonBackReference
    Theatre theatre;
    
    // 修改FetchType为LAZY,添加@JsonManagedReference
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "screen")
    @JsonManagedReference
    Set<SeatCategory> seatCategories = new HashSet<>();
    
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "screen")
    Set<Seat> seats = new HashSet<>();

    // 添加辅助方法维护双向关联
    public void addSeatCategory(SeatCategory seatCategory) {
        seatCategories.add(seatCategory);
        seatCategory.setScreen(this);
    }
}

SeatCategory实体

@Entity
@Table(name = "seat_category")
public class SeatCategory {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private String seatCategoryId;
    private String name;
    private int capacity;
    private int price;
    
    // 添加@JsonBackReference解决序列化循环
    @ManyToOne
    @JoinColumn(name = "screen_id")
    @JsonBackReference
    Screen screen;
}

服务方法修改

public ResponseEntity<Theatre> createTheatre(CreateTheatreDTO createTheatreDTO, String userName){
    Optional<User> user = userRepository.findByUserName(userName);
    User userObj = user.orElseThrow(() -> new UsernameNotFoundException("User not found"));

    Theatre theatre = Theatre.builder()
            .name(createTheatreDTO.getName())
            .build();

    // 遍历DTO,用辅助方法维护双向关联
    createTheatreDTO.getScreens().forEach(screenDTO -> {
        Screen screen = new Screen();
        screen.setName(screenDTO.getName());

        screenDTO.getSeatCategories().forEach(seatCategoryDTO -> {
            SeatCategory seatCategory = new SeatCategory();
            seatCategory.setName(seatCategoryDTO.getName());
            seatCategory.setCapacity(seatCategoryDTO.getCapacity());
            seatCategory.setPrice(seatCategoryDTO.getPrice());
            screen.addSeatCategory(seatCategory); // 自动设置seatCategory的screen关联
        });

        theatre.addScreen(screen); // 自动设置screen的theatre关联
    });

    theatre.setUser(userObj);
    Theatre savedTheatre = theatreRepository.save(theatre);
    return ResponseEntity.ok().body(savedTheatre);
}

关键解决点

  1. 解决JSON序列化循环引用:

    • 使用@JsonManagedReference标记双向关联的"主动"一方(OneToMany侧)
    • 使用@JsonBackReference标记"被动"一方(ManyToOne侧),该字段不会被序列化,避免循环递归
  2. 正确维护双向关联关系:

    • 在实体类中添加辅助方法(如addScreen、addSeatCategory),确保设置集合的同时自动维护反向关联(ManyToOne侧的字段)
    • 避免手动单独设置screen.setTheatre(theatre)或seatCategory.setScreen(screen),通过辅助方法统一处理
  3. 调整FetchType:

    • 将EAGER加载改为LAZY,减少不必要的递归加载,同时避免序列化时触发全量关联查询导致StackOverflow

验证效果

修改后,数据库中screen的theatre_id、seat_category的screen_id会被正确填充,接口响应中不会出现循环引用,且双向关联的业务逻辑正常生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 05:35:13