[英]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.