JPA @OneToOne select 包含 N+1 个查询的列表
JPA @OneToOne select lists with N+1 queries
我实际上是在尝试使用 JPA @OneToOne
注释来 link 一个 Child
实体到它的 Parent
。
它运行良好,除了在获取 Child
的列表时,JPA 引擎(在本例中为 Hibernate)进行 1+n 次查询。
这是 Hibernate 查询的日志:
select child0_.id as id1_0_, child0_.parent as parent3_0_, child0_.value as value2_0_ from child child0_
select parent0_.id as id1_1_0_, parent0_.something as somethin2_1_0_ from parent parent0_ where parent0_.id=?
select parent0_.id as id1_1_0_, parent0_.something as somethin2_1_0_ from parent parent0_ where parent0_.id=?
select parent0_.id as id1_1_0_, parent0_.something as somethin2_1_0_ from parent parent0_ where parent0_.id=?
使用完全相同的实体定义,特别是当我得到 child 时,JPA 使用预期的 JOIN 执行查询:
select child0_.id as id1_0_0_, child0_.parent as parent3_0_0_, child0_.value as value2_0_0_, parent1_.id as id1_1_1_, parent1_.something as somethin2_1_1_ from child child0_ left outer join parent parent1_ on child0_.parent=parent1_.id where child0_.id=?
这是 Child
实体定义:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "child")
public class Child {
@Id
private Long id;
@Column
private String value;
@OneToOne(optional = false)
@JoinColumn(name = "parent")
private Parent parent;
}
和 Parent
实体:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "parent")
public class Parent {
@Id
private Long id;
@Column
private String something;
}
您可以在此处找到 运行 代码的完整示例:
https://github.com/Alexandre-Carbenay/demo-jpa-onetoone
在获取具有 Parent
的 Child
实体列表时,有没有办法避免 1+n 查询?
我可以重现您的观察结果,但我看不出 Hibernate 这样做的原因。
避免查询的一种解决方案是使用 JOIN FETCH,例如
select c from Child c join fetch c.parent
默认情况下,@OneToOne
和 @ManyToOne
关联使用 FetchType.EAGER
,这就是您看到 N+1 query issue.
的原因
所以,解决方案相当简单,只需将获取策略设置为 LAZY 即可:
@OneToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "parent")
private Parent parent;
如果你有一个双向 @OneToOne
关联,除非你使用字节码增强,否则父端不能变得惰性。
我终于找到了一个比 JOIN FETCH
更好的解决方案,它也适用于 QueryDsl,在存储库方法上使用 @EntityGraph
注释。
这是更新后的 Child
定义:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@NamedEntityGraph(name = "Child.withParent", attributeNodes = @NamedAttributeNode("parent"))
@Table(name = "child")
public class Child {
@Id
private Long id;
@Column
private String value;
@OneToOne(optional = false)
@JoinColumn(name = "parent")
private Parent parent;
}
和 ChildJpaRepository
定义:
public interface ChildJpaRepository extends JpaRepository<Child, Long>, QueryDslPredicateExecutor<Child> {
@Override
@EntityGraph("Child.withParent")
List<Child> findAll();
@Override
@EntityGraph("Child.withParent")
List<Child> findAll(Predicate predicate);
}
感谢 Simon Martinelli 和 Vlad Mihalcea 的帮助
我实际上是在尝试使用 JPA @OneToOne
注释来 link 一个 Child
实体到它的 Parent
。
它运行良好,除了在获取 Child
的列表时,JPA 引擎(在本例中为 Hibernate)进行 1+n 次查询。
这是 Hibernate 查询的日志:
select child0_.id as id1_0_, child0_.parent as parent3_0_, child0_.value as value2_0_ from child child0_
select parent0_.id as id1_1_0_, parent0_.something as somethin2_1_0_ from parent parent0_ where parent0_.id=?
select parent0_.id as id1_1_0_, parent0_.something as somethin2_1_0_ from parent parent0_ where parent0_.id=?
select parent0_.id as id1_1_0_, parent0_.something as somethin2_1_0_ from parent parent0_ where parent0_.id=?
使用完全相同的实体定义,特别是当我得到 child 时,JPA 使用预期的 JOIN 执行查询:
select child0_.id as id1_0_0_, child0_.parent as parent3_0_0_, child0_.value as value2_0_0_, parent1_.id as id1_1_1_, parent1_.something as somethin2_1_1_ from child child0_ left outer join parent parent1_ on child0_.parent=parent1_.id where child0_.id=?
这是 Child
实体定义:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "child")
public class Child {
@Id
private Long id;
@Column
private String value;
@OneToOne(optional = false)
@JoinColumn(name = "parent")
private Parent parent;
}
和 Parent
实体:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "parent")
public class Parent {
@Id
private Long id;
@Column
private String something;
}
您可以在此处找到 运行 代码的完整示例: https://github.com/Alexandre-Carbenay/demo-jpa-onetoone
在获取具有 Parent
的 Child
实体列表时,有没有办法避免 1+n 查询?
我可以重现您的观察结果,但我看不出 Hibernate 这样做的原因。
避免查询的一种解决方案是使用 JOIN FETCH,例如
select c from Child c join fetch c.parent
默认情况下,@OneToOne
和 @ManyToOne
关联使用 FetchType.EAGER
,这就是您看到 N+1 query issue.
所以,解决方案相当简单,只需将获取策略设置为 LAZY 即可:
@OneToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "parent")
private Parent parent;
如果你有一个双向 @OneToOne
关联,除非你使用字节码增强,否则父端不能变得惰性。
我终于找到了一个比 JOIN FETCH
更好的解决方案,它也适用于 QueryDsl,在存储库方法上使用 @EntityGraph
注释。
这是更新后的 Child
定义:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@NamedEntityGraph(name = "Child.withParent", attributeNodes = @NamedAttributeNode("parent"))
@Table(name = "child")
public class Child {
@Id
private Long id;
@Column
private String value;
@OneToOne(optional = false)
@JoinColumn(name = "parent")
private Parent parent;
}
和 ChildJpaRepository
定义:
public interface ChildJpaRepository extends JpaRepository<Child, Long>, QueryDslPredicateExecutor<Child> {
@Override
@EntityGraph("Child.withParent")
List<Child> findAll();
@Override
@EntityGraph("Child.withParent")
List<Child> findAll(Predicate predicate);
}
感谢 Simon Martinelli 和 Vlad Mihalcea 的帮助