Spring Data JPA - 不同查询的不同连接

Spring Data JPA - Different joins for different queries

使用 Java 11、Spring 引导和 Spring 数据 JPA

概览

我想使用 Spring Data JPA 访问 mysql 数据库中的 3 个连接表。为了简单起见,我们称它们为学生、课程和 performance_report.

这是我的数据classes:

@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;
}
@Entity
@Table(name = "course")
@Data
public class Course {

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

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

}
@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;
}

这是我的 StudentRepository:

public interface StudentRepository extends JpaRepository<Student, Long> {

    Optional<Student> findById(Long studentId);
}

调用 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%"
    }
  ]
}

问题

我还想执行这个操作的逆运算,这样我就可以查询 Course 并得到一个这样的对象:

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

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

如果我以与 Student 相同的方式为 Course 设置联接 - 通过在 Course 中添加 @OneToMany 并添加 @JsonBackReferencePerformanceReport 中的第二个 @ManyToOne - 我不会在结果中得到任何 Student 数据。它还将阻止 Course 数据流向 Student 查询。如果我删除 @JsonBackReference 注释,我会得到无限递归和 Whosebug 错误。

我尝试创建单独的实体来说明这些情况。我从 Student 中删除了连接并将其放在扩展 Student 的 class 中。然后我对 CoursePerformanceReport 做同样的事情。这不仅会导致新的错误,而且非常混乱。它还要求我创建单独的存储库来处理这些扩展的 classes.

一定有更好的方法。

我的做法是否正确? Spring Data JPA 是完成此类任务的最佳方式吗?如果我想在不使用任何连接的情况下查询 StudentCourse 怎么办?

当然,我不需要为所有可能的场景都添加新实体。如何为不同的查询自定义连接表的方式?

你的问题与JPA关系不大,可以用更简洁的方式表达。我提出并解释了一个解决方案,它使用 - 而不是 @JsonBackrefence - @JsonView 注释。

假设我们有两个 类:

Parent:

@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;
}

Child:

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

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 进行引用时才会获取引用。也许您还需要一个不会触发 getter.

的视图

您也可以实施 JPA 投影等,但那是另一回事了。