如何使用JPA和JpaSpecificationExecutor进行结果分组?

10

我正在使用 JpaSpecificationExecutor、JPA 2.0、Hibernate 和 MSSQL,并希望使用 CriteriaBuilder 构建以下查询:

SELECT CURR_DATE, MAX(POSITION) FROM TOP_COMPONENT_HISTORY GROUP BY CURR_DATE

我的问题:可能吗?如果可能,怎么做呢?

感谢你花时间思考这个问题!

以下是我的代码...

表格(TOP_COMPONENT_HISTORY)

1   ARC_ID  varchar NO          
2   CURR_DATE   varchar NO          
3   REG_DATE    datetime2   YES         7
4   APPLY_DATE  datetime2   YES         7
5   POSITION    int YES 10  0   
6   REG_USER_ID varchar NO          
7   MOD_USER_ID varchar NO  

服务

public Page<TopComponentHistory> findByCurrDate(ArticleSearchForm searchForm){
        return topComponentHistoryRepository.findAll(TopComponentHistory.findAllGroupBy(),constructPageSpecification(searchForm.getPageNum());
    }

域名

public class TopComponentHistory implements Serializable {
    public static Specification<TopComponentHistory> findAllGroupBy() {     
       How can i make query...
       return ..
    }
}

仓库

public interface TopComponentHistoryRepository extends JpaRepository<TopComponentHistory, String>, JpaSpecificationExecutor<TopComponentHistory> {


}

我很难理解你最初的查询。它缺少一个FROM子句,而且我不确定你想如何处理TITLE - mabi
更新后的查询语句无效。它解决了 FROM 问题,但是您仍然不能选择在 GROUP BY 子句中未包含的原始列。您能描述一下您实际想要实现什么吗? - mabi
我想使用JPA和JpaSpecificationExecutor按结果进行分组。 - 박주성
3个回答

4

我使用一个带有一些自定义规格的通用类,它有点混乱但可以工作。

它适用于根实体中的列名称,如果你需要按其他列进行分组,就必须修改代码,但这段代码可以作为起点。

public class GenericSpecifications<T> {


public Specification<T> groupBy(Specification<T> specification, List<String> columnNames) {
    return new Specification<T>() {

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

            List<Expression<?>> columnNamesExpression = columnNames.stream().map(x -> root.get(x))
                    .collect(Collectors.toList());

            query.groupBy(columnNamesExpression);
            return specification.toPredicate(root, query, criteriaBuilder);
        }
    };
}

举个例子,如果你需要使用分组函数的类:

GenericSpecifications<YourEntity> genericSpecifications = new GenericSpecifications<YourEntity>();
    ..
    Specification<YourEntity> specification = Specification.where(null);
    ..
    specification = genericSpecifications.groupBy(specification, names);
    YourEntityRepository.findAll(specification);

我可以看到这个,怎么才能将它和一个日期组合在一个具有日期时间字段的表中呢? - Neri

1
到目前为止,我找到的解决此问题最佳方法是扩展我的 JpaRepository 并使用 EntityManagerCriteria API 返回自定义对象。这是一个易于实现且相当有组织的解决方案。此外,它允许重用自定义存储库并只使用所需的规范。
例如,假设我们有以下规范。
public class TopComponentHistorySpecifications {

    public static Specification<TopComponentHistory> equalDate(LocaldDateTime date) {
        return (root, cq, cb) -> {
            if (date != null) return cb.equal(root.get("currDate"), date);
            else return cb.and();
        };
    }

    public static Specification<TopComponentHistory> groupByCurrDate() {
        return (root, cq, cb) -> {
            cb.groupBy(root.get("currDate"));
            return cb.and();
        };
    }

}

现在,我们将拥有以下自定义仓库和模型。
public class MaxPositionByCurrDate {

   private LocalDateTime currDate;
   private Long max;

   // Rest

}

public interface CustomTopComponentHistoryRepository {
   List<MaxPositionByCurrDate> findAllMaxPositionByCurrDate(Specification<TopComponentHistory> specifications);
}

@Repository
public class CustomTopComponentHistoryRepositoryImpl implements CustomTopComponentHistoryRepository {

   @PersistenceContext
   private EntityManager entityManager;

   @Override
   List<MaxPositionByCurrDate> findAllMaxPositionByCurrDate(Specification<TopComponentHistory> specifications) {

        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<MaxPositionByCurrDate> query = cb.createQuery(MaxPositionByCurrDate.class);
        Root<TopComponentHistory> root = query.from(TopComponentHistory.class);
        query.where(specifications.toPredicate(root, query, cb), TopComponentHistorySpecifications.groupByCurrDate().toPredicate(root, query, cb));
        query.select(cb.construct(MaxPositionByCurrDate.class, root.get("currDate"), cb.max(root.get("maxPosition"))));
        return entityManager.createQuery(query).getResultList();

   }
}

有了这个,我们可以轻松地重复使用我们的规范并按照需要进行分组。如果您要添加更多规范,可以将它们作为参数轻松添加。例如。
List<MaxPositionByCurrDate> result = this.repository.findAllMaxPositionByCurrDate(Specifications.where(
    // Add all the specifications you need, as long as the parameters needed are found on the group by clause
));

重要提示:我假设您知道如何扩展JpaRepository,如果不知道,可以在这里查看。


-1
public static Specification<TopComponentHistory> findAllGroupBy() {
        return new Specification<TopComponentHistory>(){
            @Override
            public Predicate toPredicate(Root<TopComponentHistory> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
    query.groupBy(column_name);
    }
    }

3
解释一下就好了。 - Aziz
JpaSpecificationExecutor拥有方法List<T> findAll(Specification<T> spec),我们可以将Specification<T>作为参数传递。Specification可以通过重写toPredicate(Root, CriteriaQuery, CriteriaBuilder)方法创建。我们可以创建任意数量的Predicates,并使用query.grouBy()进行GROUP BY子句。 - DarkCrow
2
我花了一些时间才弄清楚如何找到在group by中定义列名的正确方法。因此,在Deepak的示例中,将query.groupBy(column_name);替换为query.groupBy(root.get(TopComponentHistory_.YOUR_COLUMN)); - Xavier Bouclet
2
返回语句在哪里? - Oleg Mikhailov
它正在返回新的Specification<TopComponentHistory>。 - DarkCrow
1
我认为@OlegMikhailov的意思是要求toPredicate方法的返回语句,而不是findAllGroupBy方法的。显然缺失了这个返回语句。 - Jad B.

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