QueryDSL JPA和PostgreSQL

时间:2016-05-12 14:12:26

标签: java hibernate postgresql querydsl

我目前正在使用带有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)));

所以基本上我想获取Child具有codeValue代码和空日期的任何Parent对象。当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

从我可以收集的内容中,(subQuery.list(qChild)中的任何()。是引起问题的部分。从我发现的所有例子来看,这就是正在做的事情 - 但它也有一个代理键。我有什么东西可以在这里找到吗?

场景2:

第二种情况是处理仅PostgreSQL功能--pg_trgm扩展名。此扩展用于模糊搜索,并允许我们使用两个特定命令 - "相似度(x,y)"和" x%y"。前者将返回表示参数匹配程度的实数,如果值大于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));

这完美无缺,但遗憾的是相似性(x,y)&#34;没有使用三元组索引,所以我想改为&#34;%&#34;运营商,这样做。我认为它应该像以下一样简单:

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需要String而不是Boolean。排除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

例外情况似乎指向&#34;存在&#34;,但我不确定原因。结果,我尝试创建一个子查询(如方案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. 使用复合键制作子查询的正确方法是什么?
  4. 我们也非常感谢任何其他指示或帮助。

1 个答案:

答案 0 :(得分:1)

想出如何做到这两点(虽然我还没有回答场景2中的所有问题)。我的问题是我想要返回实体。一个美好的夜晚让我清楚地看到它。

我应该返回其父ID,而不是从子查询列表中返回qChild。然后我只需要检查父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为我的trigram运算符保留了自定义Dialect,并使用了以下子查询:

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)));

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