[英]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 中的用戶,默認情況下不會加載它,因此您需要將其添加到查詢中:
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);
}
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.