子查询中的 JPA CriteriaQuery select 中不需要的交叉连接
Unwanted Cross Join in JPA CriteriaQuery select in subquery
当我在 SUBQUERY 中执行 select 时,我得到了我认为不必要的 CROSS JOIN,这会损害性能。如果有区别,我正在使用 Postgres。
我的目标是生成以下查询
select a1.first_name from author a1
where a1.last_name = ?
and (a1.id in
(select distinct b.author_id
from book b
where (b.published_on between ? and ?)
group by b.author_id
having count(b.author_id) >= 2))
但是我明白了
select a1.first_name from author a1
where a1.last_name = ?
and (a1.id in
(select distinct b.author_id
from book b
cross join author a2 where b.author_id = a2.id -- <<< I don't want this cross join!
and (b.published_on between ? and ?)
group by b.author_id
having count(b.author_id) >= 2))
代码
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String> cq = cb.createQuery(Author.class);
Root<Author> authorRoot = cq.from(Author.class);
Subquery<Long> countSubquery = cq.subquery(Long.class);
Root<Book> bookRoot = countSubquery.from(Book.class);
Expression<Long> count = cb.count(bookRoot.get(Book_.author));
countSubquery.select(bookRoot.get(Book_.AUTHOR))
.distinct(true)
.where(cb.between(bookRoot.get(Book_.publishedOn),
LocalDate.of(2021, MARCH, 1),
LocalDate.of(2021, MARCH, 31)))
.groupBy(bookRoot.get(Book_.author))
.having(cb.greaterThanOrEqualTo(count, 2L));
cq.where(
cb.equal(authorRoot.get(Author_.lastName), "Smith"),
cb.in(authorRoot.get(Author_.ID)).value(countSubquery));
cq.select(authorRoot.get(Author_.FIRST_NAME));
TypedQuery<String> query = entityManager.createQuery(cq);
return query.getResultList();
实际上我是从用户驱动的查询生成器生成查询的,此代码重现了我遇到的确切问题。
当使用查询生成器时,用户最终可能会在子查询中得到多个 select,因此我需要它尽可能地执行。
我不明白为什么我需要任何 join/cross 连接才能使我的查询正常工作。
实体
@Entity
public class Author {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Book> books;
}
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;
private LocalDate publishedOn;
}
此表达式:bookRoot.get(Book_.author)
表示您正在隐式地将 Author
加入 Book
。
要摆脱额外的连接,您将不得不使用本机查询,或者再次将 Book.author_id
映射为一个简单的列:
@Column(name = "author_id", insertable = false, updatable = false)
private Long authorId;
并改用 Book_.authorId
。
当我在 SUBQUERY 中执行 select 时,我得到了我认为不必要的 CROSS JOIN,这会损害性能。如果有区别,我正在使用 Postgres。
我的目标是生成以下查询
select a1.first_name from author a1
where a1.last_name = ?
and (a1.id in
(select distinct b.author_id
from book b
where (b.published_on between ? and ?)
group by b.author_id
having count(b.author_id) >= 2))
但是我明白了
select a1.first_name from author a1
where a1.last_name = ?
and (a1.id in
(select distinct b.author_id
from book b
cross join author a2 where b.author_id = a2.id -- <<< I don't want this cross join!
and (b.published_on between ? and ?)
group by b.author_id
having count(b.author_id) >= 2))
代码
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String> cq = cb.createQuery(Author.class);
Root<Author> authorRoot = cq.from(Author.class);
Subquery<Long> countSubquery = cq.subquery(Long.class);
Root<Book> bookRoot = countSubquery.from(Book.class);
Expression<Long> count = cb.count(bookRoot.get(Book_.author));
countSubquery.select(bookRoot.get(Book_.AUTHOR))
.distinct(true)
.where(cb.between(bookRoot.get(Book_.publishedOn),
LocalDate.of(2021, MARCH, 1),
LocalDate.of(2021, MARCH, 31)))
.groupBy(bookRoot.get(Book_.author))
.having(cb.greaterThanOrEqualTo(count, 2L));
cq.where(
cb.equal(authorRoot.get(Author_.lastName), "Smith"),
cb.in(authorRoot.get(Author_.ID)).value(countSubquery));
cq.select(authorRoot.get(Author_.FIRST_NAME));
TypedQuery<String> query = entityManager.createQuery(cq);
return query.getResultList();
实际上我是从用户驱动的查询生成器生成查询的,此代码重现了我遇到的确切问题。
当使用查询生成器时,用户最终可能会在子查询中得到多个 select,因此我需要它尽可能地执行。
我不明白为什么我需要任何 join/cross 连接才能使我的查询正常工作。
实体
@Entity
public class Author {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Book> books;
}
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;
private LocalDate publishedOn;
}
此表达式:bookRoot.get(Book_.author)
表示您正在隐式地将 Author
加入 Book
。
要摆脱额外的连接,您将不得不使用本机查询,或者再次将 Book.author_id
映射为一个简单的列:
@Column(name = "author_id", insertable = false, updatable = false)
private Long authorId;
并改用 Book_.authorId
。