【问题标题】:JPA Hibernate FetchType.EAGER and FetchMode.JOIN for non primary key非主键的 JPA Hibernate FetchType.EAGER 和 FetchMode.JOIN
【发布时间】:2020-07-01 19:33:07
【问题描述】:

我遇到了一个非常有趣的场景。我知道 n+1 问题以及 FetchType.EAGER 和 FetchMode.JOIN。 我有一个父实体学校,它有 2 个@OneToMany 子实体,即学生和教师。我需要全部 3 个,所以使用 FetchType.EAGER 和 FetchMode.JOIN。

学校实体

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;
 }

学生实体

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;
}

教师实体

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;
}

学校回购

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

如果我通过findById方法得到School对象,即表的主键。

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

生成的 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=?

但是如果我找到一所学校有其他一些不是主键的变量。

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

生成的 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=?

我意识到只有在我们通过 id 即主键获取时加入才有效,但我没有 School 的主键。我有学校的名称,它是唯一的和索引的,也需要学生实体和教师实体。有没有办法让他们都在休眠中使用加入。我知道如果学生和老师的记录更多,那将是性能下降,但就我而言,最多只有 3-4 条记录。这就是为什么我想加入他们。

【问题讨论】:

    标签: java hibernate jpa spring-data-jpa spring-data


    【解决方案1】:
    • 不建议使用FetchMode.JOIN 映射一个实体的多个关联集合字段。这是为了避免 Cartesian product issue。我很惊讶即使您选择了Id,它也执行了 sql 连接

    • 当您获取除 Id 字段之外的 School 时,hibernate 不知道您将获取多少个 School,因此如果它执行连接获取而不是单独选择,它将以 Cartesian product issue

    • 假设您有 10 所学校,每所学校有 20 名教师和 400 名学生。如果 hibernate 进行了连接,它将必须从 db 中带来 80,000 (10*20*400) 记录。 但是由于是单独选择,所以会带来4,21010 + 200 + 4000)的记录。即使在由Id 选择的情况下,它也是420 记录与8000 记录

    简答

    即使您找到了一种方法,也不要使用连接检索父实体及其多个关联集合,因为性能会比多选差。

    更新:

    • 如果您确定学校名称是唯一的,并且每个学校只有几位老师并且学生人数很少,您可以执行以下操作:(当前您的findBySchoolName返回List&lt;School&gt;,您可以将其更改为返回可选学校)
    @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);
    }
    

    【讨论】:

    • 谢谢,@kavithakaran-kanapathippillai 的回复,是的,我知道如果记录很高,那将是性能下降,但在我的实际程序中,学校名称是唯一的并被索引,学生人数和教师人数不超过3-4。有什么方法可以加入他们。
    • 只要是两个或多个one to manys,不管大小还是笛卡尔。而且hibernate也不知道名字只有一所学校。
    • 而且索引也是查找记录,所以它对笛卡尔没有影响。即在这两种情况下,它都会轻松找到记录
    • @shrikant.sharma,看到更新,你可以使用那个解决方案
    猜你喜欢
    • 2011-02-18
    • 2015-06-15
    • 2012-09-02
    • 2020-02-19
    • 1970-01-01
    • 1970-01-01
    • 2018-10-19
    • 2020-01-04
    • 2015-06-17
    相关资源
    最近更新 更多