繁体   English   中英

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

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

只需面对此类 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);
}

我在日志中看到一个这样的查询

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

还有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用户 user1_ on todo0_.user_id=user1_.id where todo0_.id=?

ToDoRepr 是一个简单的 POJO。 使用接受 ToDo 实体作为参数的构造函数。

这是我在此查询中使用的两个 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。 可以通过该查询解决问题,但为什么它不能与接受实体作为参数的构造函数一起使用?

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

这是一个非常常见的问题,因此我创建了文章Eliminate Spring Hibernate N+1 查询详细介绍了解决方案

Hibernate 的最佳实践是将所有关联定义为 Lazy以避免在不需要时获取它。 更多原因,请查看 Vlad Mihalcea 的文章https://vladmihalcea.com/eager-fetching-is-a-code-smell/

为了解决您的问题,在您的 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.
}

如果您需要访问 ToDoRepr 中的用户,默认情况下不会加载它,因此您需要将其添加到查询中:

  • 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,使用EntityGraph :
public interface ToDoRepository extends CrudRepository<ToDo, Long> {

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

我想在这里收集一些关于我自己的问题的解决方法。 有一个没有显式 JPQL 查询的简单解决方案。 Spring 数据 JPA 可以将任何具有适当 getter 和 setter 的 POJO 视为投影。

这对我来说很完美

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