简体   繁体   English

JPA Hibernate 非主键的 FetchType.EAGER 和 FetchMode.JOIN

[英]JPA Hibernate FetchType.EAGER and FetchMode.JOIN for non primary key

I have come across a very interesting scenario.我遇到了一个非常有趣的场景。 I know about the n+1 problem and FetchType.EAGER and FetchMode.JOIN.我知道 n+1 问题以及 FetchType.EAGER 和 FetchMode.JOIN。 I have a parent entity School which has 2 @OneToMany children entity ie Student and Teacher.我有一个父实体学校,它有 2 个@OneToMany 子实体,即学生和教师。 I need all 3 so using FetchType.EAGER and FetchMode.JOIN.我需要全部 3 个,所以使用 FetchType.EAGER 和 FetchMode.JOIN。

school entity学校实体

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import javax.persistence.*;
import java.util.Set;
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class School {
    @Id
    @GeneratedValue(generator = "sequence", strategy = GenerationType.IDENTITY)
    @SequenceGenerator(name = "sequence", allocationSize = 10)
    int schoolId;
    String schoolName;
    float schoolRating;
    @OneToMany(mappedBy = "school", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
    @Fetch(FetchMode.JOIN)
    private Set<Teacher> teachers;
    @OneToMany(mappedBy = "school", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
    @Fetch(FetchMode.JOIN)
    private Set<Student> students;
 }

Student entity学生实体

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.util.Date;
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Student {
    @Id
    @GeneratedValue(generator = "sequence", strategy = GenerationType.IDENTITY)
    @SequenceGenerator(name = "sequence", allocationSize = 10)
    public int studentId;
    public byte studentByte;
    public Date date;
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "schoolId", referencedColumnName = "schoolId")
    private School school;
}

Teacher entity教师实体

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;    
import javax.persistence.*;
import java.util.Date;
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Teacher {
    @Id
    @GeneratedValue(generator = "sequence", strategy = GenerationType.IDENTITY)
    @SequenceGenerator(name = "sequence", allocationSize = 10)
    public int teacherId;
    public byte teacherByte;
    public Date date;
    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "schoolId", referencedColumnName = "schoolId")
    private School school;
}

School Repo学校回购

@Repository
public interface SchoolRepository extends JpaRepository<School, Integer> {
List<School>findBySchoolName(String schoolName);
}

if I get School object via the findById method, ie the primary key of the table.如果我通过 findById 方法得到 School object,即表的主键。

Optional<School> schoolById = schoolRepository.findById(1);

generated SQL is a join of school, student, and teacher entity.生成的 SQL 是学校、学生和教师实体的连接。

select school0_.schoolId as schoolid1_0_0_, school0_.schoolName as schoolna2_0_0_, school0_.schoolRating as schoolra3_0_0_, students1_.schoolId as schoolid4_1_1_, students1_.studentId as studenti1_1_1_, students1_.studentId as studenti1_1_2_, students1_.date as date2_1_2_, students1_.schoolId as schoolid4_1_2_, students1_.studentByte as studentb3_1_2_, teachers2_.schoolId as schoolid4_2_3_, teachers2_.teacherId as teacheri1_2_3_, teachers2_.teacherId as teacheri1_2_4_, teachers2_.date as date2_2_4_, teachers2_.schoolId as schoolid4_2_4_, teachers2_.teacherByte as teacherb3_2_4_ from School school0_ left outer join Student students1_ on school0_.schoolId=students1_.schoolId left outer join Teacher teachers2_ on school0_.schoolId=teachers2_.schoolId where school0_.schoolId=?

but if I find a school with some other variable which is not a primary key.但是如果我找到一所学校有一些其他变量不是主键。

List<School> schoolByName = schoolRepository.findBySchoolName("school1");

generated SQL is 3 different hits on DB.生成的 SQL 是 DB 上的 3 个不同命中。

Hibernate: select school0_.schoolId as schoolid1_0_, school0_.schoolName as schoolna2_0_, school0_.schoolRating as schoolra3_0_ from School school0_ where school0_.schoolName=?
Hibernate: select teachers0_.schoolId as schoolid4_2_0_, teachers0_.teacherId as teacheri1_2_0_, teachers0_.teacherId as teacheri1_2_1_, teachers0_.date as date2_2_1_, teachers0_.schoolId as schoolid4_2_1_, teachers0_.teacherByte as teacherb3_2_1_ from Teacher teachers0_ where teachers0_.schoolId=?
Hibernate: select students0_.schoolId as schoolid4_1_0_, students0_.studentId as studenti1_1_0_, students0_.studentId as studenti1_1_1_, students0_.date as date2_1_1_, students0_.schoolId as schoolid4_1_1_, students0_.studentByte as studentb3_1_1_ from Student students0_ where students0_.schoolId=?

I realized that join only works if we are getting by id ie primary key, but I don't have a primary key of School.我意识到只有在我们通过 id 即主键获取时加入才有效,但我没有 School 的主键。 I have the name of the school which is unique and indexed and needs student entity and teacher entity also.我有学校的名称,它是唯一的和索引的,也需要学生实体和教师实体。 is there a way to get them all using join in hibernate.有没有办法让他们全部使用 hibernate 中的连接。 I know if student and teacher records are more then it will be performance degradation, but in my case, it will be at most 3-4 records only.我知道如果学生和老师的记录更多,那将是性能下降,但就我而言,最多只有 3-4 条记录。 that's why I want to join them all.这就是为什么我想加入他们。

  • It is not advisable to map more than one associated collection fields of an Entity with FetchMode.JOIN .不建议 map 使用FetchMode.JOIN的实体的多个关联集合字段。 This is to avoid Cartesian product issue .这是为了避免Cartesian product issue I am surprised it did a sql join even when you selected by Id我很惊讶它确实加入了 sql ,即使您选择了Id

  • When you are fetching School other than it's Id field, hibernate does not know how many Schools you will be fetching, so if it did a join fetch rather than separate selects, it will end up with a Cartesian product issue当您获取除其Id字段之外的 School 时,hibernate 不知道您将获取多少个 Schools,因此如果它进行连接提取而不是单独选择,最终将导致Cartesian product issue

  • Say you have 10 schools, each school has 20 teachers and 400 students.假设你有 10 所学校,每所学校有 20 名教师和 400 名学生。 If hibernate did a join it will have to bring 80,000 ( 10*20*400 ) records from db.如果 hibernate 进行了连接,它将必须从 db 中带来80,000 ( 10*20*400 ) 条记录。 But since it is doing separate select, it will bring 4,210 ( 10 + 200 + 4000 ) records.但由于它在做单独的 select,它将带来4,210 ( 10 + 200 + 4000 ) 条记录。 Even in the case selecting by Id it is 420 records vs 8000 records即使在按Id选择的情况下,它也是420条记录与8000条记录

Short Answer简短的回答

Do not retrieve Parent entity and more than one of its associated collections using join even if you find a way to do that because performance will be worse than multiple selects.不要使用连接检索父实体及其关联的多个 collections,即使您找到了这样做的方法,因为性能会比多选差。

Update:更新:

  • If you are sure that the school name is unique and there is only a few teachers per school and students size is small, you can do the following: (currently your findBySchoolName returns List<School> , you can change that to return an optional school)如果您确定学校名称是唯一的并且每所学校只有几位老师并且学生人数很少,您可以执行以下操作:(当前您的findBySchoolName返回List<School> ,您可以将其更改为返回可选学校)
@Repository
public interface SchoolRepository extends JpaRepository<School, Integer> {
    
    @Query("SELECT s from School s left join fetch s.teachers " +
            "left join fetch s.students where s.schoolName = :name")
    Optional<School> findBySchoolName(String name);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM