使用PostMapping同时保存父/子实体并分配外键失败排查
问题:PostMapping保存多级关联实体时外键未正确分配且出现空指针异常
问题描述
通过PostMapping同时保存父对象Course、子对象HoleLayout,以及关联两者ID的子对象Hole。目前三个实体均可保存,但子对象的外键未被正确分配,且CourseServiceImpl中出现空指针异常:
Cannot invoke "java.util.List.forEach(java.util.function.Consumer)" because the return value of "com.Rest.GolfMax.API.Models.Course.getHoleLayout()" is null
尝试过双向映射、JsonConverter等方案,问题仍未解决。
相关代码
父实体Course
@Table(name = "courses") public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private long id; @OneToMany(mappedBy = "course") private List<Score> scores; @OneToMany(mappedBy = "course", fetch = FetchType.LAZY, cascade = CascadeType.ALL) private List<HoleLayout> holeLayout = new ArrayList<>(); @Column(name = "courseName", nullable = false) private String courseName; // ... getters and setters
子实体HoleLayout
@Table(name = "holeLayout") public class HoleLayout { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private long id; @OneToMany(mappedBy = "holeLayout", fetch = FetchType.LAZY, cascade = CascadeType.ALL) private List<Hole> holes = new ArrayList<>(); @ManyToOne private Course course; @Enumerated(EnumType.ORDINAL) @Column(name = "layoutType", nullable = false) private LayoutType layoutType; @Column(name = "front9Yards", nullable = false) private long front9Yards; @Column(name = "back9Yards", nullable = false) private long back9Yards; @Column(name = "overallPar", nullable = false) private int overallPar; @Column(name = "courseRating", nullable = false) private double courseRating; @Column(name = "slopeRating", nullable = false) private double slopeRating; // ... getters and setters
子实体Hole
@Table(name = "hole") public class Hole { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private long id; @ManyToOne private HoleLayout holeLayout; @Column(name = "holeNumber", nullable = false) private int holeNumber; @Column(name = "yards", nullable = false) private int yards; @Column(name = "par", nullable = false) private int par; // ... getters and setters
CourseServiceImpl代码
@Override public Course createCourse(Course course) { course.getHoleLayout().forEach(layout -> layout.setCourse(course)); for (int i = 0; i < course.getHoleLayout().size(); i++) { HOLE_REPOSITORY.save(course.getHoleLayout().get(i).getHoles().get(i)); course.getHoleLayout() .get(i) .getHoles() .forEach(hole -> hole.setHoleLayout(course.getHoleLayout().get(i))); } COURSE_REPOSITORY.save(course); HOLE_LAYOUT_REPOSITORY.saveAll(course.getHoleLayout()); return course; }
CourseController代码
public ResponseEntity<CourseDto> addNewCourse(@RequestBody @NotNull CourseDto courseDto) { Course courseRequest = modelMapper.map(courseDto, Course.class); if (COURSE_SERVICE.isValid(courseDto.getCourseName())) return new ResponseEntity<>(HttpStatus.BAD_REQUEST); Course course = COURSE_SERVICE.createCourse(courseRequest); CourseDto courseResponse = modelMapper.map(course, CourseDto.class); return new ResponseEntity<>(courseResponse, HttpStatus.CREATED); }
请求JSON示例
{ "courseName": "测试球场", "holeLayout": [ { "layoutType": 0, "front9Yards": 3200, "back9Yards": 3300, "overallPar": 72, "courseRating": 71.2, "slopeRating": 125, "holes": [ { "holeNumber": 1, "yards": 350, "par": 4 }, { "holeNumber": 2, "yards": 180, "par": 3 } ] } ] }
解决方案
1. 修复空指针异常
空指针根源是modelMapper映射时未初始化holeLayout集合,可通过两种方式解决:
- 在
Course实体的无参构造方法中初始化集合:
public Course() { this.holeLayout = new ArrayList<>(); }
- 或者在Controller映射后检查并初始化:
Course courseRequest = modelMapper.map(courseDto, Course.class); if (courseRequest.getHoleLayout() == null) { courseRequest.setHoleLayout(new ArrayList<>()); }
2. 修正关联关系维护逻辑
当前Service存在逻辑冗余与顺序错误,利用级联配置简化操作:
@Override public Course createCourse(Course course) { // 依次维护多级关联关系 course.getHoleLayout().forEach(layout -> { layout.setCourse(course); layout.getHoles().forEach(hole -> hole.setHoleLayout(layout)); }); // 仅保存Course,级联操作会自动保存HoleLayout和Hole return COURSE_REPOSITORY.save(course); }
3. 校验DTO映射配置
确保CourseDto、HoleLayoutDto、HoleDto与对应实体的集合字段名称一致,或在modelMapper中显式配置集合映射规则,避免映射时集合丢失。
4. 确认数据库外键配置
检查数据库中hole_layout表的course_id列、hole表的hole_layout_id列是否存在,且允许为空(或在保存时能被正确赋值)。
内容的提问来源于stack exchange,提问作者charbs29
相关产品推荐
相关产品推荐

