我有一个基于“项目”的 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
包含聚合字段,例如 locations、sumAmount 和 countItems。为了处理它们,我们可以用 @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/