场景
Java8新特性-Stream对集合进行操作的常用API:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/126070657
针对Stream的一个常用的业务场景:对订单表按照产品id分组,展示不用产品
的不用状态(已支付、已退款)状态下的订单笔数统计、金额统计等报表实现。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
实现
首先订单实体的关键字段
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("instant_orders")
public class InstantOrders implements Serializable{
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "主键id")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
@ApiModelProperty(value = "订单状态(PENDING_PAYMENT-待付款 PAID-已支付 COMPLETED-已完成 CANCELLED-已取消 REFUNDING-退款中 REFUNDED-已退款 CLOSED-已关闭)")
@TableField("order_status")
private InstantOrderStatusEnum orderStatus;
@ApiModelProperty(value = "支付状态(PENDING-待支付 PAID-已支付 REFUNDED-已退款)")
@TableField("pay_status")
private InstantOrderPayStatusEnum payStatus;
@ApiModelProperty(value = "产品id")
@TableField("product_id")
private String productId;
@ApiModelProperty(value = "加油升数")
@TableField("liters")
private BigDecimal liters;
@ApiModelProperty(value = "实收价格")
@TableField("reality_price")
private BigDecimal realityPrice;
}
分组统计实现
1、首先查询出符合条件的订单数据
List<InstantOrders> list = new LambdaQueryChainWrapper<>(instantOrdersMapper)
.eq(InstantOrders::getStoreId, shiftFinishDTO.getStoreId())
.gt(InstantOrders::getCreateTime,shiftFinishDTO.getStartTime())
.lt(InstantOrders::getCreateTime,shiftFinishDTO.getEndTime())
.list();
2、判空处理后根据产品id分组并批量查询产品名称
Set<String> productIds = list.stream()
.map(InstantOrders::getProductId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
//批量获取产品名称映射
Map<String, String> productNameMap = iProductService.batchGetProductNames(productIds);
3、批量根据产品id查询产品名称实现
public Map<String, String> batchGetProductNames(Collection<String> productIds) {
if (CollectionUtils.isEmpty(productIds)) {
return Collections.emptyMap();
}
// 使用MP的in查询
List<Product> products = new LambdaQueryChainWrapper<>(productMapper)
.select(Product::getId, Product::getProductName)
.in(Product::getId, productIds)
.eq(Product::getIsDelete, IsDeletedEnum.NOT_DELETED)
.list();
return products.stream()
.collect(Collectors.toMap(
Product::getId,
Product::getProductName,
(oldVal, newVal) -> oldVal // 重复key处理
));
}
4、根据产品id进行分组统计
List<OilTradingDTO> OilTradingDTOs = list.stream()
.filter(order -> order.getProductId() != null)
.collect(Collectors.groupingBy(
InstantOrders::getProductId,
Collectors.collectingAndThen(
Collectors.toList(),
orderList -> {
OilTradingDTO oilTradingDTO = new OilTradingDTO();
// 设置产品名称
oilTradingDTO.setProductName(
productNameMap.getOrDefault(
orderList.get(0).getProductId(),
"未知产品"
)
);
// 统计升数总和
oilTradingDTO.setLiter(orderList.stream()
.map(o -> o.getLiters() != null ? o.getLiters() : BigDecimal.ZERO)
.reduce(BigDecimal.ZERO, BigDecimal::add));
// 统计订单笔数
oilTradingDTO.setOrderCount(orderList.size());
// 统计实收金额(PAID状态)
oilTradingDTO.setReceivedAmount(orderList.stream()
.filter(o -> o.getPayStatus() == InstantOrderPayStatusEnum.PAID)
.map(o -> o.getRealityPrice() != null ? o.getRealityPrice() : BigDecimal.ZERO)
.reduce(BigDecimal.ZERO, BigDecimal::add));
// 统计退款金额(REFUNDED状态)
oilTradingDTO.setRefundAmount(orderList.stream()
.filter(o -> o.getPayStatus() == InstantOrderPayStatusEnum.REFUNDED)
.map(o -> o.getRealityPrice() != null ? o.getRealityPrice() : BigDecimal.ZERO)
.reduce(BigDecimal.ZERO, BigDecimal::add));
return oilTradingDTO;
}
)
))
.values()
.stream()
.collect(Collectors.toList());
其中用到的结果dto
@Data
@Builder
@AllArgsConstructor
@Accessors(chain = true)
public class OilTradingDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("产品名称")
private String productName;
@ApiModelProperty(value = "升数(升)", example = "23.18")
private BigDecimal liter;
@ApiModelProperty(value = "订单笔数", example = "3")
private Integer orderCount;
@ApiModelProperty(value = "加油实收金额(元)", example = "200.50")
private BigDecimal receivedAmount;
@ApiModelProperty(value = "退款金额(元)", example = "200.50")
private BigDecimal refundAmount;
public OilTradingDTO(){
this.liter = BigDecimal.ZERO;
this.receivedAmount = BigDecimal.ZERO;
this.refundAmount = BigDecimal.ZERO;
}
}
统计逻辑细化讲解:
过滤操作
.filter(order -> order.getProductId() != null): 过滤掉productId为null的订单,确保后续分组操作不会因null值出错。
分组操作
.collect(Collectors.groupingBy(
InstantOrders::getProductId,
Collectors.collectingAndThen(
Collectors.toList(),
orderList -> {
// 转换逻辑
}
)
))
Collectors.groupingBy(): 按产品ID分组
第一个参数InstantOrders::getProductId: 分组依据是订单的productId字段
第二个参数是下游收集器,这里使用collectingAndThen
Collectors.collectingAndThen(): 先执行第一个收集器,然后对结果应用转换函数
Collectors.toList(): 将相同产品ID的订单收集到列表中
orderList -> {...}: 对每个产品ID对应的订单列表进行转换
转换操作
这部分为每个产品ID创建并填充一个OilTradingDTO对象:
产品名称设置:
从productNameMap中查找产品名称,使用第一个订单的productId作为key
如果找不到则使用默认值"未知产品"
升数总和统计:
使用流处理所有订单的liters字段
处理null值(使用BigDecimal.ZERO替代)
使用reduce求和
订单笔数统计:
直接使用订单列表的size()
实收金额统计:
只处理支付状态为PAID的订单
同样处理null值并求和
退款金额统计:
只处理支付状态为REFUNDED的订单
处理null值并求和