Search code examples
javaspringspring-bootspring-data-jpahibernate-mapping

Spring Data JPA : persist a many to may relationship in both directions


I managed to persist a many to many relationship on the child side, (mappedBy), and it works just fine, as shown below:


Student Entity (Owner)

package com.main.manytomany.models;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "students")
@Getter
@Setter
@NoArgsConstructor
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;


    @JsonIgnore
    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    @JoinTable
            (
                    name = "students_courses",
                    joinColumns = {
                            @JoinColumn
                                    (
                                            name = "student_id",
                                            referencedColumnName = "id",
                                            nullable = false,
                                            updatable = false
                                    )
                    },
                    inverseJoinColumns = {
                            @JoinColumn
                                    (
                                            name = "course_id",
                                            referencedColumnName = "id",
                                            nullable = false,
                                            updatable = false
                                    )
                    }
            )
    private Set<Course> courses = new HashSet<>();


}

Course Entity (Child)

package com.main.manytomany.models;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "courses")
@Getter@Setter
@NoArgsConstructor
public class Course {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "courses", fetch = FetchType.LAZY)
    private Set<Student> students = new HashSet<>();
}

Student Service

package com.main.manytomany.services;

import com.main.manytomany.models.Student;
import com.main.manytomany.repositories.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class StudentService {

    private final StudentRepository studentRepository;

    @Autowired
    public StudentService(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

    public List<Student> findAll() {
        return this.studentRepository.findAll();
    }

    public Student getOneById(Long id) {
        return this.studentRepository.getOne(id);
    }

    public void store(Student student) {

        this.studentRepository.save(student);
    }
}

Course Service

package com.main.manytomany.services;

import com.main.manytomany.models.Course;
import com.main.manytomany.models.Student;
import com.main.manytomany.repositories.CourseRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class CourseService {

    private final CourseRepository courseRepository;
    private final StudentService studentService;

    @Autowired
    public CourseService(CourseRepository courseRepository, StudentService studentService) {
        this.courseRepository = courseRepository;
        this.studentService = studentService;
    }

    public List<Course> findAll() {
        return this.courseRepository.findAll();
    }

    public void store(Course course) {

        course.getStudents()
                .addAll(course
                        .getStudents()
                        .stream()
                        .map(s -> {
                            Student student = studentService.getOneById(s.getId());
                            student.getCourses().add(course);
                            return student;
                        }).collect(Collectors.toList()));

        this.courseRepository.save(course);
    }
}

Student Controller | Post Mapping

@PostMapping
public ResponseEntity<Void> create(@RequestBody Student student) {
    this.studentService.store(student);
    return new ResponseEntity<>(HttpStatus.CREATED);
}

Course Controller | Post Mapping

@PostMapping
public ResponseEntity<Void> create(@RequestBody Course course) {
    this.courseService.store(course);
    return new ResponseEntity<>(HttpStatus.CREATED);
}

Student List | Postman

List of students

Course List | Postman

List of courses

Students_Courses | Pivot Table + Hibernate Query

pivot table data


How can I make it work in the owner table?

So that instead of the persistence running in the CourseService, it shall run, in the StudentService.

So that in Postman, I shall be writing something like that for insterting a student, along with his attached courses:

{
    "name" : "John Doe",
    "courses" : [
        {
            "id" : 1
        },
                {
            "id" : 2
        }
        ]
}

Solution

  • You need to cascade the changes (so pretty much the same way you did int the other direction) in the Course entity:

    @ManyToMany(mappedBy = "courses", fetch = FetchType.LAZY, cascade = {
        CascadeType.PERSIST,
        CascadeType.MERGE
    })
    private Set<Student> students = new HashSet<>();