问题描述
使用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
的类中。然后,我对Student
和Course
做同样的事情。这不仅导致新的错误,而且非常混乱。还需要我创建单独的存储库来处理这些扩展类。必须有更好的方法。
我正确地解决了吗? Spring Data JPA是完成此类任务的最佳方法吗?如果我想查询
PerformanceReport
或Student
而根本不使用任何联接怎么办?当然,对于每种可能的情况,我都不需要新的实体。如何为不同查询定制联接表的方式?
解决方法
您的问题与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预测等,但这是另一回事了。