JPA Criteria API在复杂的GROUP BY上失效

问题描述 投票:1回答:0

给定一个只包含主键和DATETIME文件'create_date'的表,我想用JPA criteria api写一个查询,从给定的日期开始计算在不同时间间隔内创建的行数(例如:2小时,12小时,3天)。intervalStart 在下面的代码中).在未来将增加额外的列,用户应该能够通过这些列过滤结果--因此需要建立动态查询。将来会添加更多的列,用户应该能够通过这些列来过滤结果--因此需要动态地建立查询。查询应该在PostgreSQL 9+上运行。

实体类。

@Entity(name = "foo")
public class Foo {

    @Id
    @Column(name = "id", nullable = false)
    private long id;

    @Column(name = "create_date", columnDefinition = "TIMESTAMP")
    private LocalDateTime createDate;
}

建立查询的代码(12小时间隔)。

public class FooQueryFactory {

  private EntityManager entityManager;

  public CriteriaQuery<Tuple> buildQuery(LocalDateTime intervalStart) {
    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Tuple> query = criteriaBuilder.createTupleQuery();

    Root<Foo> foo = query.from(Foo.class);

    Expression<Integer> dateRange = getDateRangeExpression(criteriaBuilder, foo, intervalStart);

    return query.multiselect(dateRange.alias("date_range"), criteriaBuilder.count(foo).alias("count"))
        .groupBy(dateRange)
        .orderBy(criteriaBuilder.asc(dateRange));
  }

  private Expression<Integer> getDateRangeExpression(CriteriaBuilder criteriaBuilder, Root<Foo> foo,
      LocalDateTime intervalStart) {
    long intervalLengthInSeconds = 12 * 60 * 60;
    return criteriaBuilder.function("floor", Integer.class,
        criteriaBuilder.quot(secondsSinceIntervalStart(criteriaBuilder, foo, intervalStart),
            criteriaBuilder.literal(intervalLengthInSeconds)));
  }

  private Expression<Long> secondsSinceIntervalStart(CriteriaBuilder criteriaBuilder, Root<Foo> foo,
      LocalDateTime from) {
    Expression<Long> createDateAsEpoch =
        criteriaBuilder.function("date_part", Long.class, criteriaBuilder.literal("epoch"), foo.get("createDate"));
    return criteriaBuilder.diff(getEpoch(from), createDateAsEpoch);
  }

  private long getEpoch(LocalDateTime dateTime) {
    return dateTime.atZone(ZoneId.ofOffset("UTC", ZoneOffset.ofHours(0))).toEpochSecond();
  }
}

遗憾的是,执行查询时出现了异常。

javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not extract ResultSet

    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
    at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1535)
    at org.hibernate.query.Query.getResultList(Query.java:165)
    at org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter.getResultList(CriteriaQueryTypeQueryAdapter.java:76)
    (...)
    Caused by: org.postgresql.util.PSQLException: ERROR: column "foo0_.create_date" must appear in the GROUP BY clause or be used in an aggregate function
  Position: 407
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2533)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2268)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:313)
    at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:448)
    at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:369)
    at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:159)
    at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:109)
    at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)
    at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:57)
    ... 135 more

然而,如果我把创建的SQL复制到数据库控制台,它就会成功执行并产生预期的结果。

19:15:24.369 DEBUG [main] o.h.SQL - select floor((1497106800-date_part('epoch', foo0_.create_date))/7200) as col_0_0_, count(foo0_.id) as col_2_0_ from foo foo0_ group by floor((1497106800-date_part('epoch', foo0_.create_date))/7200) order by floor((1497106800-date_part(?, foo0_.create_date))/7200) asc
19:15:24.370 TRACE [main] o.h.t.d.s.BasicBinder - binding parameter [1] as [VARCHAR] - [epoch]
19:15:24.391 WARN  [main] o.h.e.j.s.SqlExceptionHelper - SQL Error: 0, SQLState: 42803
19:15:24.392 ERROR [main] o.h.e.j.s.SqlExceptionHelper - ERROR: column "foo0_.create_date" must appear in the GROUP BY clause or be used in an aggregate function
  Position: 407
java postgresql hibernate jpa criteria-api
© www.soinside.com 2019 - 2024. All rights reserved.