Spring Data JPA-针对不同查询的不同联接

问题描述

使用Java 11,Spring Boot和Spring Data JPA

概述

我要使用Spring Data JPA访问的MysqL数据库中有3个联接表。为了简单起见,我们称它们为“学生”,“课程”和“成绩报告”。

这是我的数据类:

@Entity
@Table(name = "student")
@Data
public class Student {

    @Id
    @Column(name = "student_id")
    private Long studentId;

    @Column(name = "student_name")
    private String studentName;

    @OnetoMany(mappedBy = "student",fetch = FetchType.EAGER,cascade = CascadeType.ALL)
    private List<PerformanceReport> performanceReports;
}
@H_404_11@
@Entity
@Table(name = "course")
@Data
public class Course {

    @Id
    @Column(name = "course_id")
    private Long courseId;

    @Column(name = "course_name")
    private String courseName;

}
@H_404_11@
@Entity
@Table(name = "performance_report")
@Data
public class PerformanceReport {

    @Id
    @Column(name = "performance_report_id")
    private Long performanceReportId;

    @ManyToOne(fetch = FetchType.EAGER,optional = false)
    @JoinColumn(name = "student_id",nullable = false)
    // JsonBackReference needed to prevent infinite recursion.
    @JsonBackReference
    private Student student;

    @ManyToOne(fetch = FetchType.EAGER,optional = false)
    @JoinColumn(name = "course_id",nullable = false)
    private Course course;

    @Column(name = "grade")
    private String grade;

    @Column(name = "attendance")
    private String attendance;
}
@H_404_11@

这是我的学生资料库:

public interface StudentRepository extends JpaRepository<Student,Long> {

    Optional<Student> findById(Long studentId);
}
@H_404_11@

调用StudentRepository.findById会生成一个像这样的对象:

{
  "studentId": 1,"studentName": "Spongebob Squarepants","performanceReports": [
    {
      "performanceReportId": 5473,"course": {
        "courseId": 643,"courseName": "Boating 101"
      },"grade": "F","attendance": "100%"
    },{
      "performanceReportId": 4723,"course": {
        "courseId": 346,"courseName": "Grilling 101"
      },"grade": "A+","attendance": "100%"
    }
  ]
}
@H_404_11@

问题

我还想执行此操作的逆操作,因此我可以查询Course并获取如下对象:

{
  "courseId": 346,"courseName": "Grilling 101","performanceReports": [
    {
      "performanceReportId": 4723,"student": {
        "studentId": 1,"studentName": "Spongebob Squarepants"
      },{
      "performanceReportId": 4774,"student": {
        "studentId": 4,"studentName": "Squidward Tentacles"
      },"grade": "C-","attendance": "72%"
    }
  ]
}
@H_404_11@

我无法使用当前的实体结构执行此操作。

如果我以与Course相同的方式设置Student的联接-通过在@OnetoMany添加Course添加{{1} }到@JsonBackReference中的第二个@ManyToOne-结果中不会得到任何PerformanceReport数据。这也将防止Student数据流向Course查询。如果删除Student批注,则会出现无限递归和StackOverflow错误

我尝试创建单独的实体来解决这些情况。我从@JsonBackReference删除了该联接,并将其放入扩展了Student的类中。然后,我对StudentCourse做同样的事情。这不仅导致新的错误,而且非常混乱。还需要我创建单独的存储库来处理这些扩展类。

必须有更好的方法

我正确地解决了吗? Spring Data JPA是完成此类任务的最佳方法吗?如果我想查询PerformanceReportStudent而根本不使用任何联接怎么办?

当然,对于每种可能的情况,我都不需要新的实体。如何为不同查询定制联接表的方式?

解决方法

您的问题与JPA无关,可以用更紧凑的方式表示。我提出并说明了一种解决方案,该解决方案使用了-@JsonBackrefence注释而不是@JsonView

假设我们有两个类:

父母

@Getter @Setter
public class Parent {
    public static class ManagedReferenceView {};
    // Just to have something to show   
    private String name = "" + hashCode();
    @JsonView(ManagedReferenceView.class)
    private List<Child> children;
}

孩子

@Getter @Setter
@RequiredArgsConstructor
public class Child {
    public static class BackReferenceView {
    };
    // Just to have something to show   
    private String name = "" + hashCode();
    @JsonView(BackReferenceView.class)
    private final Parent parent;
}

如您所见,视图类不需要代码,它们只是要使用的“名称”。关键是要告诉我们什么时候要序列化。这就是它们的使用方式(例如):

构造一些父母:

Parent p = new Parent();
p.setChildren(Arrays.asList(new Child(p),new Child(p)));

序列化Parent

objectMapper.writerWithView(Parent.ManagedReferenceView.class).writeValueAsString(p);

序列化Child

objectMapper.writerWithView(Child.BackReferenceView.class).writeValueAsString(p);

对于问题的JPA优化部分,您可以在连接中添加fetch = FetchType.LAZY,这可以让Hibernate决定从db获取引用是否明智。在最佳情况下,直到objectMapper在序列化时需要调用getter进行引用之前,都不会提取引用。也许您还需要一个不会触发吸气剂的视图。

您还可以实现JPA预测等,但这是另一回事了。