JPA CriteriaQuery 中不需要的交叉联接在子查询中选择

问题描述 投票:0回答:2

当我在子查询中进行选择时,我得到了我认为不必要的交叉连接,这会损害性能。如果这有什么区别的话,我正在使用 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();

实际上,我是从用户驱动的查询生成器生成查询,此代码重新创建了我遇到的确切问题。

使用查询生成器时,用户最终可能会在子查询中进行多次选择,因此我需要它尽可能地执行。

我不明白为什么我需要任何联接/交叉联接才能让我的查询工作。

实体


@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;
    
}

java sql jpa criteria criteria-api
2个回答
3
投票

此表达式:

bookRoot.get(Book_.author)
表示您隐式地将
Author
加入到
Book

要摆脱额外的连接,您必须使用本机查询,或再次将

Book.author_id
映射为简单列:

@Column(name = "author_id", insertable = false, updatable = false)
private Long authorId;

并使用

Book_.authorId
代替。


0
投票

您的问题只需使用

join
而不是
get
中的
bookRoot.get(Book_.author)
即可解决。您在没有显式连接的情况下引用
Author
(关系属性,而不是基本属性)。这将使持久性提供者生成
cross join
而不是
inner join

© www.soinside.com 2019 - 2024. All rights reserved.