簡體   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