Querydsl: when groupBy with two fields,fetchCount throw exception

Created on 19 Dec 2019  路  9Comments  路  Source: querydsl/querydsl

here is my code:

query.groupBy(_entity.field1,_entity.field2).fetchCount()

then the exception:

java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE, found ',' near line 1, column 56 [select count(distinct entity.field1, entity.field2)
from com.demo.core.domain.model.entity entity
where entity.field3 = ?1

    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:133)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:157)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:164)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:670)
    at org.hibernate.internal.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:23)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:305)
    at com.sun.proxy.$Proxy119.createQuery(Unknown Source)
    at com.querydsl.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:101)
    at com.querydsl.jpa.impl.AbstractJPAQuery.fetchCount(AbstractJPAQuery.java:81)
    at com.demo.core.infrastructure.repository.impl.queryRepositoryImpl.query(queryRepositoryImpl.java:91)
    at com.demo.core.infrastructure.repository.impl.queryRepositoryImplTest.query(queryRepositoryImplTest.java:25)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE, found ',' near line 1, column 56 [select count(distinct entity.field1, entity.field2)
from com.demo.core.domain.model.rebate.entity entity
where entity.field3 = ?1
    at org.hibernate.hql.internal.ast.QuerySyntaxException.convert(QuerySyntaxException.java:74)
    at org.hibernate.hql.internal.ast.ErrorCounter.throwQueryException(ErrorCounter.java:91)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:291)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:186)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:141)
    at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:115)
    at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:77)
    at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:153)
    at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:553)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:662)
    ... 41 more

and when I remove one field in the groupBy, the same code will success

so what's the problem?

bug hibernate jpa

All 9 comments

I have same problem. Please help!

fetchCount() uses a COUNT function, which is an aggregate function. Your query already has aggregate functions. You cant aggregate aggregate functions, unless a subquery is used (which is not available in JPA). Therefore this use case cannot be supported.

Duplicate of #2053

My temporary solution

@SneakyThrows
protected <E> Page<E> pageDTOs(JPAQuery<E> jpaQuery, Long pageNumber, Integer pageSize) {
    if (jpaQuery.getMetadata().getGroupBy().size() > 1) {
        // Fixed https://github.com/querydsl/querydsl/pull/2605/files
        // Get JPQLSerializer
        var serializer = (JPQLSerializer) $.bean.invoke(jpaQuery,
                JPAQueryBase.class.getDeclaredMethod("serialize", boolean.class), false);
        String hql = serializer.toString();
        // HQL to Native SQL
        var translatorFactory = new ASTQueryTranslatorFactory();
        var hibernateSession = entityManager.getEntityManagerFactory().unwrap(SessionFactory.class);
        var translator = translatorFactory.
                createQueryTranslator("", hql, Collections.EMPTY_MAP, hibernateSession.getSessionFactory(), null);
        translator.compile(Collections.EMPTY_MAP, false);
        var sql = translator.getSQLString();
        // Package COUNT
        var countSql = "select count(1) from (" + sql + ") _tmp_" + System.currentTimeMillis();
        var nativeQuery = entityManager.createNativeQuery(countSql);
        // Add sql parameters
        JPAUtil.setConstants(nativeQuery, serializer.getConstantToAllLabels(), jpaQuery.getMetadata().getParams());
        // Fetch total number
        var totalNumber = ((BigInteger) (nativeQuery.getSingleResult())).longValue();
        // Fetch paginated records
        var objs = jpaQuery
                .limit(pageSize)
                .offset(pageNumber == 1 ? 0 : pageNumber * pageSize)
                .fetch();
        return Page.build(pageNumber, pageSize, totalNumber, objs);
    } else {
        var objs = jpaQuery
                .limit(pageSize)
                .offset(pageNumber == 1 ? 0 : pageNumber * pageSize)
                .fetchResults();
        return Page.build(pageNumber, pageSize, objs.getTotal(), objs.getResults());
    }
}

That approach works only with Hibernate - not plain JPA, and only if there is a one-to-one mapping between your HQL parameters and SQL parameters, which isn't always the case (for example when a named parameter is reused in the query).

If you need reliable count queries I encourage you to use Blaze-Persistence, which has an awesome mechanism of converting any query to its count query. It also integrates with Querydsl (the blaze-persistence-querydsl module): https://persistence.blazebit.com/documentation/1.5/core/manual/en_US/index.html#querydsl-integration .

Using a BlazeJPAQuery instead of a JPAQuery both fetchResults and fetchCount work reliably again.

When should we expect a fix for 2504

Not, its going to be removed as it cannot be implemented.

Why this is not mentioned in the documentation? How are people supposed to use it?

Because this method was originally intended for just querydsl-sql and was ported over to JPA very poorly. It inherently can't be supported in JPA, and therefore we will log warnings in the future.

I guess the answer is: don't rely on querydsl-jpa to make your count queries for you, use blaze-persistence-querydsl instead.

I've updated the javadoc with the rationale and alternatives: https://github.com/querydsl/querydsl/pull/2605/files#diff-63e9a52b83b73925263ae9ef7ad9372d4e76e8eceb9b2233139325bee46d8605R81-R102

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Ghalleb picture Ghalleb  路  4Comments

viktorgt picture viktorgt  路  3Comments

rafaelszp picture rafaelszp  路  5Comments

timowest picture timowest  路  7Comments

d-schmidt picture d-schmidt  路  5Comments