1

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

这是我的学生存储库:

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

我不能用我当前的实体结构来做到这一点。

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

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

一定会有更好的办法。

我正确地接近这个吗?Spring Data JPA 是完成此类任务的最佳方式吗?如果我想查询StudentCourse根本不使用任何连接怎么办?

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

4

1 回答 1

1

您的问题与 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 以供参考时才会获取引用。也许您还需要一个不会触发 getter 的视图。

您也可以实现 JPA 投影等,但这是另一回事。

于 2020-09-20T09:50:40.083 回答