【问题标题】:Unwanted Cross Join in JPA CriteriaQuery select in subqueryJPA CriteriaQuery中不需要的交叉连接在子查询中选择
【发布时间】:2021-06-17 07:45:01
【问题描述】:

当我执行 select IN SUBQUERY 时,我得到了我认为不必要的 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();

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

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

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

实体


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

【问题讨论】:

  • Criteria API 仅生成一个 JPQL 查询,我在该部分中看不到任何条件逻辑,这意味着您可以跳过它并直接使用 JPQL 查询。如果您必须生成各种wherehavingorder by 条件,那么使用它是有意义的。就像现在一样,您不需要它。
  • TypedQuery&lt;Author&gt; 会给你带来麻烦。返回的类型是String,而不是Author
  • @coladict TypedQuery 确实可以工作/编译,并成功返回字符串!我认为这是由于运行时类型擦除
  • 代码更新为 CriteriaQuery 和 TypedQuery

标签: java sql jpa criteria criteria-api


【解决方案1】:

此表达式:bookRoot.get(Book_.author) 表示您正在隐式加入 AuthorBook

要摆脱额外的连接,您必须使用本机查询,或者将Book.author_id 再次映射为一个简单的列:

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

改用Book_.authorId

【讨论】:

  • 感谢@crizzis - 我将 Long authorId 设为只读属性,以确保它无法更改。这可以很好地工作,因为“author_id”是作者作者连接列的默认名称。我可能会更新示例以在作者作者上添加一个 JoinColumn 以使这一点更清楚。
猜你喜欢
  • 2015-08-01
  • 2012-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-31
  • 2020-04-27
  • 2021-11-09
  • 2020-03-12
相关资源
最近更新 更多