使用 Hibernate Projection 的预期 N+1 查询
Posted
技术标签:
【中文标题】使用 Hibernate Projection 的预期 N+1 查询【英文标题】:Not expected N+1 queries with Hibernate Projection 【发布时间】:2019-11-03 12:24:44 【问题描述】:用这样的 Spring Data repository 面对 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);
我在日志中看到一个这样的查询
休眠: 选择 todo0_.id 为 col_0_0_ 从 待办事项 todo0_ 在哪里 todo0_.user_id=? ]
还有N个这样的查询
休眠: 选择 todo0_.id 为 id1_0_0_, todo0_.description 作为descript2_0_0_, todo0_.target_date 作为 target_d3_0_0_, todo0_.user_id 作为 user_id4_0_0_, user1_.id 为 id1_1_1_, user1_.password 作为密码2_1_1_, user1_.username 作为 username3_1_1_ 从 待办事项 todo0_ 左外连接 用户 user1_ 在 todo0_.user_id=user1_.id 在哪里 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);
【问题讨论】:
你有@OneToMany(mappedBy = "user" ...)
,这使得关系是双向的。您是否需要在User
类中映射此关系?从User
中删除todos
可能会避免这样的问题。
无论如何,这与我的问题无关。也没有通过 Hibernate 映射创建我的情况不需要的第三个表。
【参考方案1】:
这是一个非常常见的问题,因此我创建了文章 Eliminate Spring Hibernate N+1 queries 详细介绍了解决方案
使用 Hibernate 的最佳做法是将所有关联定义为 Lazy,以避免在不需要时获取它。 更多原因,请查看 Vlad Mihalcea 的文章https://vladmihalcea.com/eager-fetching-is-a-code-smell/
为了解决您的问题,在您的 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);
【讨论】:
【参考方案2】:我想在这里收集一些关于我自己的问题的解决方法。有一个没有显式 JPQL 查询的简单解决方案。 Spring Data JPA 可以将任何具有适当 getter 和 setter 的 POJO 视为投影。
正好适合我
public interface ToDoRepository extends CrudRepository<ToDo, Long>
List<ToDoRepr> findToDosByUser_Id(Long userId);
【讨论】:
以上是关于使用 Hibernate Projection 的预期 N+1 查询的主要内容,如果未能解决你的问题,请参考以下文章
使用 Hibernate Projection 的预期 N+1 查询
使用 D3 的 projection.stream() 的正确方法是啥?
ClickHouse 使用物化字段投影 PROJECTION 提升性能