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

Java 即使在使用@Fetch(FetchMode.JOIN)时,JPA+Hibernate也会出现太多查询问题,java,hibernate,spring-boot,spring-data-jpa,Java,Hibernate,Spring Boot,Spring Data Jpa,我正在使用SpringBoot开发REST应用程序,并试图优化查询的性能。我目前正在使用存储库中的findAll,这会导致性能问题。代码如下: 个人实体 @Entity @Table(name = "cd_person") @Data @NoArgsConstructor public class Person { .... @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn(

我正在使用SpringBoot开发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_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时,会为person表中的每一行触发select查询,以便在访问person时获取密码和角色。我知道我可以在存储库中使用@Query annotation和JOIN FETCH来强制生成单个查询,但我想知道是否还有其他方法可以这样做。我正在寻找一些我们可以在实体级别做的事情,以减少查询

使用SpringBoot2.1.5版本和相关依赖项


注:@Data和@noargsconstuctor是龙目山注释

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

@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 (
            ?, ?, ?, ?, ?
        )

您不能使用lazy fetch并删除@fetch吗?在实体顶部使用@NamedQuery并在自定义服务中使用hibernate会话调用session.createNamedQuery即可


如果您可以不使用默认的personRepository.findAll而使用此自定义服务,那么您将运行一个优化的查询。我知道它并没有完全回答您的问题,但我的团队和我面临着完全相同的问题,我们就是这样做的。

我会让实体保持原样,并用@Query注释覆盖存储库中的findAll方法。
通过这种方式,代码重构只需要一次存储库更改,而不是实体更改。

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


只要你正确地找到了自己,你就可以。替代方法是使用或利用,但所有这些都需要在查询/会话级别进行编程干预。

最简单的代码更改是使用spring数据中的ad-hoc EntityGraph功能。只需覆盖PersonRepository的findAll并使用@EntityGraph来配置该图。此图中的所有实体都将一起提取

public interface PersonRepository extends CrudRepository<Person, Long> {

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

}

在幕后,它的工作原理类似于JOIN-FETCH。将只生成具有左联接的单个SQL。

我的建议是:

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

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

谢谢你的努力,但我想进一步优化它。批量会更好,但仍然不是最佳的。如果你能提供任何关于批处理比join性能更好的参考,我会这样做并接受答案。我不改变存储库逻辑的唯一原因是因为我必须进行代码重构,这太费劲了。如果我没有任何其他选择,我可能不得不这样做。这种过度连接获取的性能优势/劣势是什么,还是仅仅是代码行数较少?如果您将遍历所有检索到的人员并访问其密码和角色,那么性能良好,因为所有这些都已通过单个SQL select获取,没有N+1问题,我问这个是为了加入FETCH。使用@Query还是定义实体图更好?它们是一样的。它生成与连接获取相同的SQL
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();
}