Home > Software design >  Saving Parent entity and child entity at the same time, while assigning foreign keys
Saving Parent entity and child entity at the same time, while assigning foreign keys

Time:08-31

I am trying to save a parent object->(Course), a child object ->(HoleLayout), and another child object->(Hole), except this one takes the id of Course and HoleLayout, all at the same time using a PostMapping.

I have tried most of the suggestions on StackOverflow, and so far, I haven't had any luck. It saves all three entities completely fine, the only issue is the foreign id does not get assigned to any of the child objects. I'm unsure if the mistake is happening in my CourseController or my CourseServiceImpl class. I am also using an Enum which has three different variables (Championship, Mens, Womens), which is used when I attempt to save the Course entity.

So far, I have tried using bidirectional mapping and JsonConverter to convert the Hole and HoleLayout to JSON, which wasn't the answer.

Here is the JSON body that I write when attempting to save the Course entity using Postman.

This is the parent entity 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.

This is the first child entity 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

Lastly, the second child object Hole, which should have the Course and HoleLayout id.

@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. 

This is the 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;
    }

The CoursServiceImpl also throws this error when I attempt to save the Course: Cannot invoke "java.util.List.forEach(java.util.function.Consumer)" because the return value of "com.Rest.GolfMax.API.Models.Course.getHoleLayout()" is null


Lastly, this is the 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);
    }

CodePudding user response:

you need to set reference for their relationship in the child entities too. You can follow this approach:

course.getHoleLayout().forEach(layout->layout.setCourse(course));
Similarly, do it for other child:
course.getScores().forEach(score-> score.setCourse(course));

Now, save all in one go:

COURSE_REPOSITORY.save(course);

Note: make sure, your primary key values for your entites are in object form: Like this:

private Long id;

Update:
instead of writing this:

 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;

Write this:

 course.getHoleLayout().forEach(layout -> layout.setCourse(course));
        
     
        
        COURSE_REPOSITORY.save(course);
        return course;

Also, before saving them, you need to ensure that their values are null. hope will work it.

CodePudding user response:

Update, I solved the issue by adding @JsonManagedReference to my @OneToMany initializations and @JsonBackReference to the class that the foreign id was mapped to. Thank you to user404 for the help! It didn't solve the issue... but on the other hand, I ended up learning something new.

Course Entity:

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)
    @JsonManagedReference
    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)
    @JsonManagedReference
    private List<Hole> holes = new ArrayList<>();

    @ManyToOne
    @JsonBackReference
    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
  • Related