即使使用 @Fetch(FetchMode.JOIN),JPA + Hibernate 也会出现太多查询问题

Posted

技术标签:

【中文标题】即使使用 @Fetch(FetchMode.JOIN),JPA + Hibernate 也会出现太多查询问题【英文标题】:Too many queries problem with JPA + Hibernate even when using @Fetch(FetchMode.JOIN) 【发布时间】:2019-12-30 09:48:28 【问题描述】:

我正在使用 Spring Boot 开发 REST 应用程序,并且正在尝试优化查询的性能。我目前正在使用导致性能问题的存储库中的findAll。代码如下:

人物实体

@Entity
@Table(name = "cd_person")
@Data
@NoArgsConstructor
public class Person 
    ....
    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "password_id")
    @Fetch(FetchMode.JOIN)
    private Password password;
    ....
    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH)
    @JoinTable(name = "cd_person_role",
        joinColumns = @JoinColumn(name = "person_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
    @Fetch(FetchMode.JOIN)
    private Set<Role> roles = new HashSet<>();

密码实体

@Entity
@Table(name = "cd_password")
@Data
@NoArgsConstructor
public class Password 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;

    @Column(name = "password_hash", nullable = false)
    private String passwordHash;
    .......

角色实体

@Entity
@Table(name = "cd_role")
@Data
@NoArgsConstructor
public class Role 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "role_type")
    @Enumerated(EnumType.STRING)
    private RoleType roleType;
    ....

个人资料库

public interface PersonRepository extends CrudRepository<Person, Long> 

    Optional<Person> findByEmail(String email);


当我执行personRepository.findAll() 时,会针对人员表中的每一行触发选择查询,以便在我访问人员时获取密码和角色。我知道我可以在存储库中使用@Query 注释和JOIN FETCH 来强制生成单个查询,但我想知道是否还有其他方法可以这样做。我正在寻找我们可以在实体级别做的事情以减少查询。

使用spring boot 2.1.5-RELEASE版本及相关依赖。

附言。 @Data@NoArgsConstructor 是 Lombok 注释。

【问题讨论】:

【参考方案1】:

这应该可行:

public interface PersonRepository extends CrudRepository<Person, Long> 
     @Override
        @Query("SELECT p FROM Person p JOIN FETCH p.roles JOIN FETCH p.password ")
        Iterable<Person> findAll();

【讨论】:

【参考方案2】:

您应该将 @BatchSize 放在 Password 类的顶部

@Entity
@Table(name = "cd_password")
@Data
@NoArgsConstructor
@BatchSize(size = 50)
public class Password 
...

以下是带有@BatchSize 的查询:

Hibernate: 
    select
        person0_.id as id1_1_,
        person0_.password_id as password2_1_ 
    from
        cd_person person0_
Hibernate: 
    select
        password0_.id as id1_0_0_,
        password0_.password_hash as password2_0_0_ 
    from
        cd_password password0_ 
    where
        password0_.id in (
            ?, ?, ?, ?, ?
        )

【讨论】:

感谢您的努力,但我想进一步优化它。批处理会更好,但仍然不是最佳的。如果您可以提供任何关于批处理性能如何优于连接的参考,我会这样做并接受答案。【参考方案3】:

我的建议是:

    尝试重构并使用延迟获取。 我可能不太了解这部分,但你为什么需要personRepository.findAll()?我认为您只需要personRepository.findById() 之类的东西,这样您就可以轻松获取角色和其他数据。在这里选择所有人似乎是一个巨大的负担。 您以后可能需要JpaRepository 的扩展功能,因此现在可能值得更改它,而不是稍后再工作。

【讨论】:

【参考方案4】:

最小的代码更改是使用 spring data 中的 ad-hoc EntityGraph 功能。只需覆盖 PersonRepositoryfindAll() 并使用 @EntityGraph 来配置图形。此图中的所有实体将一起获取。

public interface PersonRepository extends CrudRepository<Person, Long> 

    @EntityGraph(attributePaths =  "password", "roles" )
    public List<Person> findAll();


在幕后它的工作方式类似于JOIN FETCH。只会生成带有 LEFT JOIN 的单个 SQL。

【讨论】:

与 JOIN FETCH 相比有什么性能优势/劣势,还是代码行数更少? 如果您将遍历所有检索到的人员并访问其密码和角色,则性能良好,因为所有这些都已通过单个 SQL select 获取,没有 N+1 问题?? 我在问这个与 JOIN FETCH 相比。使用@Query 好还是定义实体图好? 它们是一样的。它生成与 JOIN FETCH 相同的 SQL【参考方案5】:

对您的问题的不满意答案是:不,无法注释/配置实体,以便获取模式也适用于查询。

您正确地找到了自己,您可以manipulate the query itself。对此的替代方法是使用 Hibernate's fetch profiles 或利用 JPA entity graphs - 但它们都需要在查询/会话级别进行编程干预。

【讨论】:

【参考方案6】:

我将保留实体并使用@Query 注释覆盖存储库中的findAll 方法。 这样,代码重构最少(仅更改一个存储库而不是更改实体)。

【讨论】:

【参考方案7】:

您不能使用延迟提取并删除 @Fetch 吗?在您的实体之上使用 @NamedQuery 并使用休眠会话在自定义服务中调用 session.createNamedQuery 即可。

如果您负担得起不使用默认的personRepository.findAll() 但此自定义服务您将运行优化查询。我知道它并不能完全回答您的问题,但我和我的团队遇到了完全相同的问题,我们就是这样做的。

【讨论】:

我不更改存储库逻辑的唯一原因是我必须进行代码重构,这太费力了。如果我没有其他选择,我可能不得不这样做。

以上是关于即使使用 @Fetch(FetchMode.JOIN),JPA + Hibernate 也会出现太多查询问题的主要内容,如果未能解决你的问题,请参考以下文章

即使在 HQL 查询中使用 Join Fetch,休眠延迟初始化异常

使用 fetch 时拒绝承诺 [重复]

了解如何使用 Node.JS fetch [重复]

Core data multithreading fetch record

laravel 中的 Http Post 使用 fetch api 给出 TokenMismatchException

如何在带有 Jest 的 react-native 中使用模拟的 fetch() 对 API 调用进行单元测试