子查询中的 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