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); }
关键解决点
解决JSON序列化循环引用:
- 使用
@JsonManagedReference标记双向关联的"主动"一方(OneToMany侧) - 使用
@JsonBackReference标记"被动"一方(ManyToOne侧),该字段不会被序列化,避免循环递归
- 使用
正确维护双向关联关系:
- 在实体类中添加辅助方法(如
addScreen、addSeatCategory),确保设置集合的同时自动维护反向关联(ManyToOne侧的字段) - 避免手动单独设置
screen.setTheatre(theatre)或seatCategory.setScreen(screen),通过辅助方法统一处理
- 在实体类中添加辅助方法(如
调整FetchType:
- 将EAGER加载改为LAZY,减少不必要的递归加载,同时避免序列化时触发全量关联查询导致StackOverflow
验证效果
修改后,数据库中screen的theatre_id、seat_category的screen_id会被正确填充,接口响应中不会出现循环引用,且双向关联的业务逻辑正常生效。
内容的提问来源于stack exchange,提问作者Reckon Mazumdar
相关产品推荐
相关产品推荐

