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

How can I do a subquery in a JPA Criteria Builder Select statment?

我正在尝试使用 CritierBuilder/CrtieriaQuery 对 select 来自 table A 的某些字段执行 select 语句,如果该记录存​​在于另一个中,则使用布尔标志table。

基本上,我有一个 "officers" 列表和一个用户列表。用户是使用该系统的人,并且能够 bookmark/save 一名官员。当用户查询官员时,我希望能够显示他们收藏了哪些官员。

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 数据库控制台中,它(非常)有效。 returns 如果该官员被用户 123456789 收藏为书签,则为真,否则为书签列为空。

但是我在运行将其放入 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 语句的子查询部分以及如何返回布尔值。

问题是 cb.isNotNull(subRoot.get("id")) 只有在 subquery 结果存在时才有效(仅 returns 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();
}