在Spring Data JPA规范中,是否可能添加count(*)和 groupBy?

4

我正在使用Spring Boot和Spring Data Jpa编写应用程序。

我有一个方法,使用RSQL(我使用这个库)和规范来进行过滤调用,将where子句传递给数据库。

我的存储库:

public interface BaseRepository<M extends BaseModel> extends JpaRepository<M, Long>, JpaSpecificationExecutor<M>{

}

在这个服务中,我调用了仓库:
String filter = "createdDate > 2019-09-04T11:52:59.449";
return repository.findAll(toSpecification(filter), pageable).getContent();  

我使用上述库生成规范,然后将其传递给存储库。

现在我需要按某些字段对结果进行分组。

问题是如何向规范中添加计数分组?或者我需要创建一个谓词,在其中定义此计数分组(某种方式),并从此创建规范?有什么更好的方法吗?

更新#0

我已成功将分组部分添加到结果SQL查询中。我创建了一个新的规范对象,在其中定义了所有分组参数,然后将此规范与原始规范组合。

String filter = "createdDate > 2019-09-04T11:52:59.449";

Specification<M> groupBySpec = new Specification<M>() {

            @Override
            public Predicate toPredicate(Root<M> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {

                List<Expression<?>> groupByParams = new ArrayList<>();

                groupByParams.add(root.get("param1"));
                groupByParams.add(root.get("param2"));

                query.groupBy(groupByParams);

                return query.getGroupRestriction();
            }
        };


return repository.findAll(groupBySpec.and(toSpecification(filter)), pageable).getContent();     

问题在于它总是选择所有列,但我需要只选择那些在group by部分的列。
结果会得到一个SQL异常:ERROR: column "columnName" must appear in the GROUP BY clause or be used in an aggregate function.
我已经尝试使用这样的多选项multiselect:criteriaQuery.multiselect(root.get("column1"), root.get("column2")).distinct(true).getGroupRestriction(); 但它仍然选择所有列。
如果我使用 @Query("query") 它可以工作,但我需要使用Criteria Api/Specification操作。

你按什么分组,能给我们看一下你正在查询的类的片段代码吗? - locus2k
@locus2k 应该可以根据从请求的http参数中获取的不同字段进行分组(例如orderBy = field1,field2,field3)。实际上,过滤器的工作方式就是这样(我有一个http参数filter = field1,field2...,我解析它),用于创建sql查询的where子句(问题中的静态字符串仅用作示例)。我尝试查询的类非常简单:它只包含一些字段,并且没有与其他模型的关系。 - Coffemanz
我问这个问题是因为看起来RSQL仅适用于WHERE子句进行过滤,而不适用于分组。Spring可以执行groupBy,但您必须编写查询以了解要查询的字段。 - locus2k
@locus2k 是的,我明白RSQL只适用于WHERE。问题是如何将分组添加到已由库生成的结果规范中。您具体指的是什么样的查询? - Coffemanz
1
我明白了。你可以查看https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/,看看它是否提供了你想要的操作方法。 - locus2k
显示剩余2条评论
2个回答

5
您可以创建自己的GroupBySpecification类,该类将groupBy表达式添加到其他规范中:
public class GroupBySpecification implements Specification {
  Specification original;
  Parameters parameters; // whatever data needed for calculating groupBy expression

  public GroupBySpecification(Specification original, Parameters parameters){
      this.original = original;
      this.parameters = parameters;
  }
  public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {

      Expression<?> expression = ...; // whatever logic needed to be used on parameters...
      criteriaBuilder.groupBy(expression);
      return original.toPredicate(root, query, criteriabuilder);
  }

然后你可以像这样使用它:

String filter = "createdDate > 2019-09-04T11:52:59.449";
return repository.findAll(new GroupBySpecification(toSpecification(filter),parameters),
   pageable).getContent();  

如果您需要计数,则调用相应的存储库方法:

repository.count(new GroupBySpecification(toSpecification(filter),parameters));  

谢谢你的回答。实际上我已经做了同样的事情,但是我没有为规范创建一个单独的类,而是在我的服务中创建一个新的规范对象,然后使用规范类中的 and 方法进行组合 (specObject.and(anotherSpecObject))。我会更新我的问题以包含这个信息。 关于 repository.count() 方法。不确定它是否符合我的需求,因为它只返回结果列表中模型的数量,而我需要它作为一个 聚合函数(像 count(*) 一样,并将一个列附加到结果集)。 - Coffemanz
虽然我使用这种方法还有另一个问题。尽管我已经在结果 SQL 查询中添加了 group by,但我需要指定用于 group by 部分的特定列才能返回,否则我会得到一个 SQL 异常“ERROR: column "columnName" must appear in the GROUP BY clause or be used in an aggregate function”。我尝试使用 CriteriaQuery 的方法 multiselect,但它仍然选择所有列。因此,需要找出如何使用 Criteria Api 选择特定列,因为如果我使用 @Query("query") 就可以正常工作,但这不是我的情况。 - Coffemanz
@Coffemanz 我和你处于同样的情况,我需要一个带有count()聚合函数的自定义对象,但是这种方式似乎不可能。我不知道它是否可行。 - Ricardo Fernandes

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接