Spring Data JPA:在两个方向上保持多对可能的关系

Posted

技术标签:

【中文标题】Spring Data JPA:在两个方向上保持多对可能的关系【英文标题】:Spring Data JPA : persist a many to may relationship in both directions 【发布时间】:2020-09-25 20:00:20 【问题描述】:

我设法在子端保持了多对多关系 (mappedBy),它工作得很好,如下所示:


学生实体(所有者)

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<>();



课程实体(儿童)

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<>();


学生服务

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);
    

课程服务

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);
    


学生控制器 |贴图

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

课程控制器 |贴图

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


学生名单 |邮递员

课程列表 |邮递员

Students_Courses |数据透视表 + 休眠查询


我怎样才能让它在所有者表中工作?

这样一来,持久性不会在 CourseService 中运行,而是应该在StudentService中运行。

所以在 Postman 中,我会写一些类似的东西来指导学生,连同他的附加课程


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

【问题讨论】:

当您向学生发布课程时,您的应用会发生什么情况? 【参考方案1】:

不使用 - mappedBy 很好用(如果我们想保留一个实体作为父实体),您可以在两个表中使用 @JoinTable 并定义相应的方法来保存数据。

因此,更改下面的映射和 StudentService 中的 store 方法以保存课程将起作用。

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

示例:

@ManyToMany(fetch=FetchType.LAZY,cascade=CascadeType.PERSIST,CascadeType.DETACH,CascadeType.REFRESH,CascadeType.MERGE)
@JoinTable(name="course_student", joinColumns=@JoinColumn(name="course_id"), inverseJoinColumns=@JoinColumn(name="student_id"))
private List<Student> students;


@ManyToMany(fetch=FetchType.LAZY,cascade=CascadeType.PERSIST,CascadeType.DETACH,CascadeType.REFRESH,CascadeType.MERGE)
@JoinTable(name="course_student", joinColumns=@JoinColumn(name="student_id"), inverseJoinColumns=@JoinColumn(name="course_id"))
private List<Course> courses;

【讨论】:

你为什么要这样做?这引入了大量的注释。它不可读且难以维护 我已经提到了....最好在一个实体中使用它,Cascade 有助于其余的。我就是这样回答的,也行。 如果没有专业人士,解决方案只会更糟,为什么还要麻烦......【参考方案2】:

您需要在Course 实体中级联更改(与您在另一个方向上所做的几乎相同):

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

【讨论】:

以上是关于Spring Data JPA:在两个方向上保持多对可能的关系的主要内容,如果未能解决你的问题,请参考以下文章

如何与 Spring Data REST 和 JPA 保持双向关系?

使用spring boot和spring data jpa时一对多关系的奇怪行为

spring data jpa 自定义sql in查询加判断

外键在一对多关系中始终为空 - Spring Boot Data with JPA

有没有办法在输出中获得可重复的主键并在 spring data jpa 中保持分页?

带有额外列的 Spring Data JPA 多对多