有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

带有JPA标准API的java“不在`subquery`”语句中

我在JavaEE应用程序中使用JPA和CriteriaAPI来查询我的数据库(PostgreSQL)。 我实现了一个树作为闭包表,并尝试获取根节点。 这是我的模式(省略无用字段):

NeedsTreev2     :
id              | primary key

NeedNode        :
id              | primary key
needstree_id    | foreign key references needstreev2(id)

NeedLink        :
ancestor_id     | foreign key references neednode(id)
descendant_id   | foreign key references neednode(id)
needstree_id    | foreign key references needstreev2(id)

这是我的JPA映射

public class NeedsTreev2 {
    @Id
    private Long id;
}

public class NeedNode {
    @Id
    private Long id;
}

public class NeedLink {
    @ManyToOne
    private NeedNode ancestor;
    @ManyToOne
    private NeedNode descendant;
    @ManyToOne
    private NeedsTreev2;
}

树的根节点是那些从不用作子节点的节点,因此 下面是返回指定树的根节点的SQL查询:

SELECT nNode.* FROM neednode nNode
               INNER JOIN needstreev2 nTree
                       ON nNode.needstree_id = nTree.id

               WHERE nTree.id = ?
                 AND nNode.id NOT IN
                (SELECT nLink.descendant_id FROM needlink nLink
                                            WHERE nLink.ancestor_id != nLink.descendant_id)
               ;

然后我试着用标准来翻译它:

public List<NeedNode> getRootsByTree(NeedsTreev2 tree) {
        List<NeedNode> ret;

        CriteriaBuilder cb = this.getEntityManager().getCriteriaBuilder();
        CriteriaQuery<NeedNode> cq = cb.createQuery(NeedNode.class);

        Root<NeedNode> nNode = cq.from(NeedNode.class);

        /* Here we define the subquery */
        Subquery<NeedNode> sq = cq.subquery(NeedNode.class);
        Root<NeedLink> nLink = sq.from(NeedLink.class);
        sq.where(cb.notEqual(nLink.get(NeedLink_.ancestor), nLink.get(NeedLink_.descendant)));
        sq.select(nLink.get(NeedLink_.descendant));
        /* End of subquery */

        Predicate[] p = {
            cb.equal(nNode.get(NeedNode_.needsTree), tree),
            cb.not(cb.in(nNode).value(sq)) /* This is where the problem occurs */
        };

        cq.where(cb.and(p));

        TypedQuery<NeedNode> query = this.getEntityManager().createQuery(cq);
        ret = query.getResultList();

        return (ret);
    }

这段代码在我看来似乎合乎逻辑,但它引发了一个异常:

org.eclipse.persistence.exceptions.QueryException
Exception Description: Illegal use of getField() [NEEDNODE.ID] in expression.
Query: ReadAllQuery(referenceClass=NeedNode )

我还试图用cb.not(nNode.in(sq))替换cb.not(cb.in(nNode).value(sq)),但它引发了相同的异常

我可能错过了什么,但我找不到。 谢谢你的帮助


共 (1) 个答案

  1. # 1 楼答案

    我就是这样解决这个问题的:我主要选择子查询中的NeedNode.id,而不是完整对象。这样,IN语句就可以工作了

    public List<NeedNode> getRootsByTree(NeedsTreev2 tree) {
        List<NeedNode> ret;
    
        CriteriaBuilder cb = this.getEntityManager().getCriteriaBuilder();
        CriteriaQuery<NeedNode> cq = cb.createQuery(NeedNode.class);
    
        Root<NeedNode> nNode = cq.from(NeedNode.class);
    
        Subquery<Long> sq = cq.subquery(Long.class);
        Root<NeedLink> nLink = sq.from(NeedLink.class);
        Join<NeedLink, NeedNode> d = nLink.join(NeedLink_.descendant, JoinType.INNER);
        sq.where(cb.notEqual(nLink.get(NeedLink_.ancestor), nLink.get(NeedLink_.descendant)));
        sq.select(d.get(NeedNode_.id));
        sq.distinct(true);
    
        Predicate[] p = {
            cb.equal(nNode.get(NeedNode_.needsTree), tree),
            nNode.get(NeedNode_.id).in(sq).not()
        };
    
        cq.where(cb.and(p));
    
        TypedQuery<NeedNode> query = this.getEntityManager().createQuery(cq);
        ret = query.getResultList();
    
        return (ret);
    }
    

    因此,代码需要一个Join变量,子查询返回Long而不是NeedNode。它是这样工作的,尽管我不明白为什么它不能像问题中写的那样工作。在我看来,让子查询选择ids会使Criteria查询失去一点“类型安全”的特性