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
并添加 @JsonBackReference
到 PerformanceReport
中的第二个 @ManyToOne
- 我不会在结果中得到任何 Student
数据。它还将阻止 Course
数据流向 Student
查询。如果我删除 @JsonBackReference
注释,我会得到无限递归和 Whosebug 错误。
我尝试创建单独的实体来说明这些情况。我从 Student
中删除了连接并将其放在扩展 Student
的 class 中。然后我对 Course
和 PerformanceReport
做同样的事情。这不仅会导致新的错误,而且非常混乱。它还要求我创建单独的存储库来处理这些扩展的 classes.
一定有更好的方法。
我的做法是否正确? Spring Data JPA 是完成此类任务的最佳方式吗?如果我想在不使用任何连接的情况下查询 Student
或 Course
怎么办?
当然,我不需要为所有可能的场景都添加新实体。如何为不同的查询自定义连接表的方式?
你的问题与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 投影等,但那是另一回事了。
使用 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
并添加 @JsonBackReference
到 PerformanceReport
中的第二个 @ManyToOne
- 我不会在结果中得到任何 Student
数据。它还将阻止 Course
数据流向 Student
查询。如果我删除 @JsonBackReference
注释,我会得到无限递归和 Whosebug 错误。
我尝试创建单独的实体来说明这些情况。我从 Student
中删除了连接并将其放在扩展 Student
的 class 中。然后我对 Course
和 PerformanceReport
做同样的事情。这不仅会导致新的错误,而且非常混乱。它还要求我创建单独的存储库来处理这些扩展的 classes.
一定有更好的方法。
我的做法是否正确? Spring Data JPA 是完成此类任务的最佳方式吗?如果我想在不使用任何连接的情况下查询 Student
或 Course
怎么办?
当然,我不需要为所有可能的场景都添加新实体。如何为不同的查询自定义连接表的方式?
你的问题与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 投影等,但那是另一回事了。