如何在 JPA Criteriabuilder Select 语句中执行子查询?

Posted

技术标签:

【中文标题】如何在 JPA Criteriabuilder Select 语句中执行子查询?【英文标题】:How can I do a subquery in a JPA Criteria Builder Select statment? 【发布时间】:2020-02-04 14:06:18 【问题描述】:

我正在尝试使用 CritierBuilder/CrtieriaQuery 执行选择语句以从表 A 中选择某些字段,然后如果该记录存​​在于另一个表中,则使用布尔标志。

基本上,我有一个“官员”列表和一个用户列表。用户是使用系统的人,并且能够为官员添加书签/保存。当用户查询官员时,我希望能够显示他们已添加书签的官员。

SELECT o.FIRST_NAME, o.LAST_NAME,
 (select CAST(1 AS BIT) from OFFICER_BOOKMARK b where b.OFFICER_ID=o.OFFICER_ID AND USER_ID=123456789) as BOOKMARKED 
from OFFICER o;

所以这个查询,我在我的 h2 数据库控制台中运行,它(相当)有效。如果该官员被用户 123456789 标记为书签,则返回 true,否则为书签列返回 null。

但我无法将其转换为 jpa 标准查询...

public List<OfficerDTO> getOfficersDto() 
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<OfficerDTO> cq = cb.createQuery(OfficerDTO.class);
    Root<OfficerEntity> root = cq.from(OfficerEntity.class);
    Root<OfficerBookmarkEntity> subRoot = cq.from(OfficerBookmarkEntity.class);

    Subquery<Boolean> subquery = cq.subquery(Boolean.class);
    subRoot.alias("bookmarked");
    subquery.select(cb.isNotNull(subRoot.get("id")));
    subquery.where(cb.equal(subRoot.get("officer").get("officerId"), root.get("officerId")));
    subquery.where(cb.equal(subRoot.get("user").get("userId"), "123456789"));

    cq.multiselect(
            cb.construct(
                OfficerDTO.class,
                root.get("firstName"),
                root.get("lastName"),
                subquery.getSelection().as(Boolean.class)
            )
    );
    TypedQuery<OfficerDTO> q = em.createQuery(cq);
    return q.getResultList();

我想我已经很接近了,但我无法弄清楚 select 语句的子查询部分以及如何返回一个布尔值。

【问题讨论】:

【参考方案1】:

问题是cb.isNotNull(subRoot.get("id")) 仅在存在subquery 结果时才有效(仅返回true)。否则你有null。所以你必须在更高的水平上检查subquery 结果。

这应该可以工作

  public List<OfficerDTO> getOfficersDto() 
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<OfficerDTO> cq = cb.createQuery(OfficerDTO.class);
        Root<OfficerEntity> root = cq.from(OfficerEntity.class);

        Subquery<Long> subquery = cq.subquery(Long.class); // or Integer (depends on id class)
        Root<OfficerBookmarkEntity> subRoot = 
            subquery.from(OfficerBookmarkEntity.class);

        Predicate officerPredicate = cb.equal(
            subRoot.get("officer").get("officerId"), 
            root.get("officerId")
        );

        Predicate userPredicate = cb.equal(
            subRoot.get("user").get("userId"), 
            "123456789"
        );

        subquery.select(subRoot.get("id"))                 // select subRoot id
            .where(officerPredicate, userPredicate);       // if you execute `.where` twice 
                                                           // it replaces the previously added restrictions     
        cq.multiselect(
            cb.construct(
                OfficerDTO.class,
                root.get("firstName"),
                root.get("lastName"),
                subquery.getSelection().isNotNull()        // check if subquery result is present
            )
        );

        return em.createQuery(cq).getResultList();

【讨论】:

谢谢!那行得通!我还不得不更改为使用休眠而不是 eclipselink...你知道 eclipselink 是否支持 select 语句中的子选择吗?这个问题和文档有点像它,***.com/questions/36077435/… 我对 eclipselink 不是很有经验

以上是关于如何在 JPA Criteriabuilder Select 语句中执行子查询?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 JPA Criteriabuilder Select 语句中执行子查询?

如何告诉 JPA CriteriaBuilder 按返回的唯一数据列排序?

JPA CriteriaBuilder - 如何使用“IN”比较运算符

java-jpa-criteriaBuilder使用入门

JPA Eclipselink子查询在where子句之间,CriteriaBuilder和元模型

真正动态的 JPA CriteriaBuilder