简体   繁体   English

预计不会使用 Hibernate 投影进行 N+1 次查询

[英]Not expected N+1 queries with Hibernate Projection

Just face the N+1 query problem with such Spring Data repository只需面对此类 Spring 数据存储库的 N+1 查询问题

public interface ToDoRepository extends CrudRepository<ToDo, Long> {

    @Query("select new com.package.repr.ToDoRepr(t) from ToDo t " +
            "where t.user.id = :userId")
    List<ToDoRepr> findToDosByUserId(@Param("userId") Long userId);
}

I see in the logs one such query我在日志中看到一个这样的查询

Hibernate: select todo0_.id as col_0_0_ from todos todo0_ where todo0_.user_id=? Hibernate: select todo0_.id as col_0_0_ from todos todo0_ where todo0_.user_id=? ] ]

And N such queries还有N个这样的查询

Hibernate: select todo0_.id as id1_0_0_, todo0_.description as descript2_0_0_, todo0_.target_date as target_d3_0_0_, todo0_.user_id as user_id4_0_0_, user1_.id as id1_1_1_, user1_.password as password2_1_1_, user1_.username as username3_1_1_ from todos todo0_ left outer join users user1_ on todo0_.user_id=user1_.id where todo0_.id=? Hibernate: select todo0_.id as id1_0_0_, todo0_.description as descript2_0_0_, todo0_.target_date as target_d3_0_0_, todo0_.user_id as user_id4_0_0_, user1_.id as id1_1_1_, user1_.password as password2_1_1_, user1_.username as username3_1_1_ from todos todo0_ left outer join用户 user1_ on todo0_.user_id=user1_.id where todo0_.id=?

ToDoRepr is a simple POJO. ToDoRepr 是一个简单的 POJO。 With constructor which accepts ToDo entity as a parameter.使用接受 ToDo 实体作为参数的构造函数。

Here are two JPA entities I use in this query这是我在此查询中使用的两个 JPA 实体

@Entity
@Table(name = "todos")
public class ToDo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String description;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    @Column
    private LocalDate targetDate;

    // geters, setters, etc.
}
@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String username;

    @Column(nullable = false)
    private String password;

    @OneToMany(
            mappedBy = "user",
            cascade = CascadeType.ALL,
            orphanRemoval = true
    )
    private List<ToDo> todos;

    // geters, setters, etc.
}

UPD. UPD。 It possible to resolve the problem by that query but why it's not working with constructor which accepts entity as a parameter?可以通过该查询解决问题,但为什么它不能与接受实体作为参数的构造函数一起使用?

public interface ToDoRepository extends CrudRepository<ToDo, Long> {

    @Query("select new com.package.repr.ToDoRepr(t.id, t.description, t.user.username, t.targetDate) " +
            "from ToDo t " +
            "where t.user.id = :userId")
    List<ToDoRepr> findToDosByUserId(@Param("userId") Long userId);
}

This is a very common question so I created the article Eliminate Spring Hibernate N+1 queries detailing the solutions这是一个非常常见的问题,因此我创建了文章Eliminate Spring Hibernate N+1 查询详细介绍了解决方案

The best practice with Hibernate is to define all associations as Lazy to avoid fetching it when you don't need it. Hibernate 的最佳实践是将所有关联定义为 Lazy以避免在不需要时获取它。 For more reasons, check Vlad Mihalcea's article https://vladmihalcea.com/eager-fetching-is-a-code-smell/更多原因,请查看 Vlad Mihalcea 的文章https://vladmihalcea.com/eager-fetching-is-a-code-smell/

For fixing your issue, in your class ToDo, you should define the ManyToOne as Lazy :为了解决您的问题,在您的 class ToDo 中,您应该将 ManyToOne 定义为 Lazy

@Entity
@Table(name = "todos")
public class ToDo {

    ...

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    ...

    // geters, setters, etc.
}

If you need to access the user in your ToDoRepr, it will not be loaded by default so you need to add it to your query:如果您需要访问 ToDoRepr 中的用户,默认情况下不会加载它,因此您需要将其添加到查询中:

  • JPQL, use JOIN FETCH : JPQL,使用 JOIN FETCH
public interface ToDoRepository extends CrudRepository<ToDo, Long> {

    @Query("select new com.package.repr.ToDoRepr(t) " +
            "from ToDo t " +
            "inner join fetch t.user " +
            "where t.user.id = :userId")
    List<ToDoRepr> findToDosByUserId(@Param("userId") Long userId);
}
  • JPA, use EntityGraph : JPA,使用EntityGraph :
public interface ToDoRepository extends CrudRepository<ToDo, Long> {

    @EntityGraph(attributePaths = {"user"})
    List<ToDoRepr> findToDosByUser_Id(Long userId);
}

I want to collect here some workaround about this my own question.我想在这里收集一些关于我自己的问题的解决方法。 There is a simple solution without explicit JPQL queries.有一个没有显式 JPQL 查询的简单解决方案。 Spring Data JPA can treat any POJO with proper getters and setters as a projection. Spring 数据 JPA 可以将任何具有适当 getter 和 setter 的 POJO 视为投影。

Just that works perfectly for me这对我来说很完美

public interface ToDoRepository extends CrudRepository<ToDo, Long> {

    List<ToDoRepr> findToDosByUser_Id(Long userId);
}

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

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