QueryDSL JPA 和 PostgreSQL 的集合查询问题

Collection Query Issues with QueryDSL JPA and PostgreSQL

我目前正在使用带有 Spring-Data-JPA 的 QueryDSL 3.7.2。我正在构建谓词,然后将其传递到扩展 QueryDslPredicateExecutor 并查询 PostgreSQL 9.3 数据库的存储库。

我的一切工作正常,但 运行 有两种情况导致我出现问题。我不确定我是否正确地处理了这个问题,而且我似乎找不到任何与场景完全匹配的示例。

场景 1:

为此,我有一个包含子项列表的实体。我想在两个子属性上使用“AND”,所以我使用了 JPASubQuery:

final QChild any = QParent.parent.children.any();
final JPASubQuery subQuery = new JPASubQuery();
final QChild qChild = QChild.child;
subQuery.from(qChild)
        .where(qChild.code.eq(codeValue)
            .and(qChild.date.isNull()));
predicateBuilder.and(any.in(subQuery.list(qChild)));

所以基本上我想获取任何 Parent 对象,其中 Child 的代码为 codeValue 且日期为空。当 Child 在数据库中有一个代理键(一个 ID 列)时,这非常有效。这在传递到存储库时生成了以下查询:

select 
    count(parent0_.parent_id) as col_0_0_ 
from parent_tab parent0_ 
where exists (
    select 1 
    from child_tab child1_ 
    where parent0_.parent_id=child1_.parent_id 
    and (
        child1_.child_id 
        in (
            select child2_.child_id 
            from child_tab child2_ 
            where child2_.status=? and (child2_.date is null)
        )
    )
)

当我们将代理键更改为具有几个字段(代码、状态、parent_id 和名称)的自然键时,问题就出现了。然后生成以下查询:

select 
    count(parent0_.parent_id) as col_0_0_ 
from parent_tab parent0_ 
where exists (
    select 1 
    from child_tab child1_ 
    where parent0_.parent_id=child1_.parent_id 
    and (
        (child1_.code, child1_.status, child1_.parent_id, child1_.name) 
        in (
            select (child2_.code, child2_.status, child2_.parent_id, child2_.name) 
            from child_tab child2_ 
            where child2_.status=? and (child2_.date is null)
        )
    )
)

这无效,并抛出以下异常:

ERROR: subquery has too few columns

据我所知,any().in(subQuery.list(qChild)) 是导致问题的部分。从我发现的所有例子中,这就是正在做的事情——但它也有一个代理键。我在这里遗漏了什么吗?

场景 2:

第二种情况是处理 PostgreSQL 独有的功能 - pg_trgm 扩展。这个扩展用于模糊搜索,并允许我们使用两个特定的命令——“similarity(x, y)”和“x % y”。前者将 return 一个实数,表示参数的匹配程度,第二个将 return 一个布尔值,如果值高于 0.3(默认情况下)。本来我用的是前者,像这样:

final NumberExpression<Double> nameExpression = NumberTemplate.create(Double.class, "similarity({0}, {1})", QParent.parent.name ConstantImpl.create(name));
predicateBuilder.or(nameExpression.goe(0.3));

这非常有效,但不幸的是“similarity(x, y)”不使用三元组索引,所以我想更改为“%”运算符,它确实如此。我认为它应该像下面这样简单:

final BooleanExpression nameExpression = BooleanTemplate.create("{0} % {1}", QParent.parent.name, ConstantImpl.create(name));
predicateBuilder.or(nameExpression.isTrue());

不幸的是,这不起作用,并抛出以下异常:

java.lang.IllegalArgumentException: Parameter value [true] did not match expected type [java.lang.String (n/a)]
    at com.mysema.query.jpa.impl.JPAUtil.setConstants(JPAUtil.java:55) [querydsl-jpa-3.7.2.jar:]
    at com.mysema.query.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:130) [querydsl-jpa-3.7.2.jar:]
    at com.mysema.query.jpa.impl.AbstractJPAQuery.count(AbstractJPAQuery.java:81) [querydsl-jpa-3.7.2.jar:]
    at org.springframework.data.jpa.repository.support.QueryDslJpaRepository.findAll(QueryDslJpaRepository.java:141) [spring-data-jpa-1.9.2.RELEASE.jar:]

问题似乎是它期望 AbstractJPAQuery 中的 JavaType 是字符串而不是布尔值。排除 isTrue() 会导致以下异常:

org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node: % near line 3, column 19

查询如下:

select count(parent)
from com.test.Parent parent
where parent.name % ?1

我通过使用自定义方言解决了这个问题,并注册了以下函数:

registerFunction("sim", new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, "?1 % ?2"));

然后我可以使用以下内容:

final BooleanExpression nameExpression = BooleanTemplate.create("sim({0}, {1})", QParent.parent.name, ConstantImpl.create(name));
predicateBuilder.or(nameExpression.isTrue());

在对此进行扩展时,我还想检查子实体的名称。这导致了以下结果:

final BooleanExpression childExpression  = BooleanTemplate.create("sim({0}, {1})", QParent.parent.children.any().name, ConstantImpl.create(name));
predicateBuilder.or(childExpression.isTrue());

然而,这不起作用,并抛出以下异常:

org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node: exists near line 3, column 7

查询是:

select count(parent)
from com.test.Parent parent
where exists (select 1
from parent.children as parent_children_4652c
where sim(parent_children_4652c.name, ?1)) = ?2

异常似乎指向“存在”,但我不确定为什么。结果,我尝试创建一个子查询(如场景 1):

final BooleanExpression childExpression  = BooleanTemplate.create("sim({0}, {1})", QChild.child.name, ConstantImpl.create(name));
final QChild child = QChild.child;
final JPASubQuery subQuery = new JPASubQuery();
subQuery.from(child)
    .where(childExpression.isTrue());
predicateBuilder.or(QParent.parent.children.any().in(subQuery.list(child)));

然而,这 运行 与场景 1 存在相同的问题,其中子实体具有复合键,而 any().in() 似乎不正确。

所以这里有几个问题:

  1. 有没有办法在不在方言中注册自定义函数的情况下使用 % 运算符?
  2. 我是否使用 sim() 函数正确查询子项?
  3. 使用复合键创建子查询的正确方法是什么?

任何额外的指示或帮助也将不胜感激。

想出了如何做到这两点(虽然我还没有回答场景 2 中的所有问题)。我的问题是我想返回实体。睡个好觉让我看清了。

不是从子查询列表中返回 qChild,我应该返回它的父 ID。然后我只需要检查父 ID 是否在该列表中:

final String code = "code";
final String name = "name";
final JPASubQuery subQuery = new JPASubQuery();
final QParent parent = QParent.parent;
final QChild child = QChild.child;
subQuery.from(child)
        .where(child.id.code.eq(code)
                .and(child.id.name.eq(name)));
predicateBuilder.or(parent.id.in(subQuery.list(child.id.parentId)));

对于第二种情况,我为三元运算符保留了带有 registerFunction 的自定义方言,并使用了以下子查询:

final String name = "name";
final QParent parent = QParent.parent;
final QChild child = QChild.child;
final BooleanExpression newExpression  = BooleanTemplate.create("sim({0}, {1})",
        child.id.name, ConstantImpl.create(name));
final JPASubQuery subQuery = new JPASubQuery();
subQuery.from(child)
        .where(newExpression.isTrue());
predicateBuilder.or(parent.id.in(subQuery.list(child.id.parentId)));

一切正常,但我仍然想知道是否有一种方法可以在不注册自定义函数的情况下简单地使用三元运算符。