【问题标题】:Spring Data JPA - Different joins for different queriesSpring Data JPA - 不同查询的不同连接
【发布时间】:2021-01-06 11:44:51
【问题描述】:

使用 Java 11、Spring Boot 和 Spring Data JPA

概述

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

这是我的数据类:

@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 并在 @987654333 中的第二个 @ManyToOne 中添加 @JsonBackReference @ - 我的结果中不会有任何 Student 数据。它还将阻止Course 数据流向Student 查询。如果我删除 @JsonBackReference 注释,我会得到无限递归和 StackOverflow 错误。

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

一定有更好的办法。

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

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

【问题讨论】:

    标签: java json serialization spring-data-jpa circular-reference


    【解决方案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 投影等,但这是另一回事。

    【讨论】:

    • 谢谢。在研究和实验方面进行了几次尝试,但我终于让它与@JsonView 一起工作。谢谢你的回答!
    猜你喜欢
    • 2016-01-06
    • 2020-10-10
    • 2017-10-25
    • 2017-04-21
    • 2017-08-04
    • 2017-11-28
    • 1970-01-01
    • 2019-12-25
    • 1970-01-01
    相关资源
    最近更新 更多