java - 具有规范和分页支持的 Spring 数据分组

标签 java sql hibernate spring-data-jpa

我有一个基于“项目”的 View ,其中一个项目属于一个容器,而一个容器由多个项目组成。此外,一个项目有一个位置,多个项目可以有相同的位置。

数据库 View :

id_item   id_container   id_location   container_name   container_code   amount
'I1'      'C1'           'L1'          'container #01'  'c01'            10
'I2'      'C1'           'L1'          'container #01'  'c01'             5
'I3'      'C1'           'L2'          'container #01'  'c01'            25
'I4'      'C2'           'L3'          'container #02'  'c02'            30

我想选择按容器分组:

按容器的实体组:

@Entity
public class GroupByContainerEntity {
    private String idContainer;
    private String containerName;
    private String containerCode;
    private List<String> locations; // OR private String locations; -> comma separated ids
    private Integer sumAmount;
    private Integer countItems;
}

存储库:

public interface IGroupByContainerRepository extends JpaRepository<GroupByContainerEntity, String>, JpaSpecificationExecutor<GroupByContainerEntity> {
}

我需要传递附加规范(例如,仅某些位置和容器)和分页(例如,按容器名称排序),因此( native )查询方法不起作用:

groupByContainerRepository.findAll(Specification, Pageable)

有什么方法可以加载按容器分组的数据(通过 spring 数据存储库)? 规范和分页支持是强制性的。

最佳答案

尽管 Specification 类旨在处理 where 子句,但在 Specification 类中指定 group by 子句也有效。

为了规避Specification::toPredicate方法必须返回一个Predicate实例的问题,你可以创建一个Specification where子句等于1=1,同时在方法中声明group by子句,就像下面的例子:

public class GroupBySpecification implements Specification<GroupByContainerEntity> {

    @Override
    public Predicate toPredicate(Root<GroupByContainerEntity> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        query.groupBy(root.get("containerCode"));
        return criteriaBuilder.equal(criteriaBuilder.literal(1), criteriaBuilder.literal(1));
    }

}

可以单独指定其他附加规范,例如位置和容器代码:

@RequiredArgsConstructor
public class LocationSpecification implements Specification<GroupByContainerEntity> {

    private final String location;


    @Override
    public Predicate toPredicate(Root<GroupByContainerEntity> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        return criteriaBuilder.equal(root.get("idLocation"), location);
    }
}
@RequiredArgsConstructor
public class ContainerCodeSpecification implements Specification<GroupByContainerEntity> {

    private final String containerCode;

    @Override
    public Predicate toPredicate(Root<GroupByContainerEntity> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        return criteriaBuilder.equal(root.get("containerCode"), containerCode);
    }

}

通过这样做,您可以重用 groupByContainerRepository.findAll(Specification, Pageable) 来满足您的需求。同样给出service层中的示例方法如下:

public Page<GroupByContainerEntity> findAll(ItemSearchParameter params, Pageable pageable) {

    List<Pair<Optional<?>, Function<Object, Specification<GroupByContainerEntity>>>> pairs = List.of(
            Pair.of(params.getLocation(), s -> new LocationSpecification((String) s)),
            Pair.of(params.getContainerCode(), c -> new ContainerCodeSpecification((String) c))
    );

    Specification<GroupByContainerEntity> spec = pairs.stream()
            .filter(entry -> entry.getFirst().isPresent())
            .map(entry -> entry.getSecond().apply(entry.getFirst().get()))
            .reduce(new GroupBySpecification(), Specification::and);

    return repository.findAll(spec, pageable);
}

值得一提的是,实体类 GroupByContainerEntity 包含聚合字段,例如 locationssumAmountcountItems。为了处理它们,我们可以用 @Formula 注释这些字段并提供查询。因此,GroupByContainerEntity类需要进一步修改如下:

@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "item")
public class GroupByContainerEntity {

    @Id
    private String idItem;

    private String idContainer;

    private String idLocation;

    private String containerName;

    private String containerCode;

    @Formula("(GROUP_CONCAT(id_location))")
    private String locations;

    @Formula("(SUM(amount))")
    private Integer sumAmount;

    @Formula("(COUNT(container_code))")
    private Integer countItems;

}

我也把上面的代码推送到了gitlab .该项目使用 SQLite 作为数据库,因此您可以克隆并尝试。

关于java - 具有规范和分页支持的 Spring 数据分组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72575712/

相关文章:

java - 递归数据库查询以使用 Hibernate - Java 获取分层结果

java - 如何在 spring boot 中启用/禁用特定的休息端点?

java - 使用Jpanel、repaint()方法在类之间进行通信

java - 使用泛型 java 实现接口(interface)

c# - 使用 SQL Server 存储数据

java - 外键空指针

java - 我们应该为 object.toArray() 使用类型转换吗?

php - SQL 更新 - 如何切换?

sql - 查找特定值出现的次数

java - 在我的 Spring Hibernate 项目上获取 InvokingTargetException