一 批量查询的常规使用与调优
1.1 反例

1.2 优化场景
2.优雅的解法:批量查询+stream组装map
第一步:查列表,拿到所有订单
第二步:提取ID集合,批量查询关联数据,用 Stream 转成 Map
第三步:循环里只从 Map 取值,零次数据库查询
对应代码案例
@Service
@Slf4j
public class OrderExportService {
public List<OrderExportVO> exportOrders(OrderQuery query) {
// ==================== 循环外面:批量查询,组装Map ====================
// ① 查订单列表(1次查询)
List<Order> orders = orderMapper.selectList(query);
if (CollectionUtils.isEmpty(orders)) {
return Collections.emptyList();
}
// ② 提取所有关联ID
Set<Long> userIds = orders.stream().map(Order::getUserId)
.filter(Objects::nonNull).collect(Collectors.toSet());
Set<Long> productIds = orders.stream().map(Order::getProductId)
.filter(Objects::nonNull).collect(Collectors.toSet());
Set<Long> shopIds = orders.stream().map(Order::getShopId)
.filter(Objects::nonNull).collect(Collectors.toSet());
Set<Long> couponIds = orders.stream().map(Order::getCouponId)
.filter(Objects::nonNull).collect(Collectors.toSet());
// ③ 批量查询 + Stream组装Map(4次查询)
Map<Long, User> userMap = batchQueryMap(userIds, userMapper::selectBatchIds, User::getId);
Map<Long, Product> productMap = batchQueryMap(productIds, productMapper::selectBatchIds, Product::getId);
Map<Long, Shop> shopMap = batchQueryMap(shopIds, shopMapper::selectBatchIds, Shop::getId);
Map<Long, Coupon> couponMap = couponIds.isEmpty()
? Collections.emptyMap()
: batchQueryMap(couponIds, couponMapper::selectBatchIds, Coupon::getId);
// ==================== 循环里面:纯内存组装,零DB查询
// ④ 遍历订单,从Map取值组装VO
return orders.stream()
.map(order -> buildExportVO(order, userMap, productMap, shopMap, couponMap))
.collect(Collectors.toList());
}
/**
* 通用的批量查询 + Map组装方法
* 传入ID集合、查询方法、Key提取器,返回 Map<id, 实体>
*/
private <T> Map<Long, T> batchQueryMap(
Collection<Long> ids,
Function<Collection<Long>, List<T>> queryFunc,
Function<T, Long> keyExtractor
) {
if (CollectionUtils.isEmpty(ids)) {
return Collections.emptyMap();
}
return queryFunc.apply(ids).stream()
.collect(Collectors.toMap(keyExtractor, Function.identity(), (a, b) -> a));
}}
二 实操mock案例
2.1 场景1
1.实体类
public class Goods {
private Long userId;
private String Name;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
}
2.goodservice类
public class GoodService {
public List<Goods> getGoodsList(Collection<Long> userIds) {
List<Goods> goodsList = new ArrayList<>();
Goods goods = new Goods();
goods.setUserId(200L);
goods.setName("商品1");
goodsList.add(goods);
Goods goods2 = new Goods();
goods2.setUserId(201L);
goods2.setName("商品2");
goodsList.add(goods2);
Goods goods3 = new Goods();
goods3.setUserId(202L);
goods3.setName("商品3");
goodsList.add(goods3);
return goodsList;
}
}
3.测试类
public class TestStream {
public static void main(String[] args) {
Set<Long> userIds = new HashSet<>();
userIds.add(200L);
userIds.add(201L);
GoodService goodService = new GoodService();
Map<Long, Goods> m = batchQueryMap(userIds, goodService::getGoodsList, Goods::getUserId);
System.out.println("m:" + m);
}
/**
* 通用的批量查询 + Map组装方法
* 传入ID集合、查询方法、Key提取器,返回 Map<id, 实体>
* 方法的参数:Collection<Long> ids,作为第二个方法的参数
* queryFunc: 调用的方法,Function<Collection<Long>, List<T>> 参数Collection<Long>为请求方法queryFunc的参数类型,和第一个参数的类型一致,
* 第二个参数为方法queryFunc的返回值类型
* keyExtractor 为返回map类型,以哪个字段为key,value为对应的实体bean
*/
private static <T> Map<Long, T> batchQueryMap(
Collection<Long> ids,
Function<Collection<Long>, List<T>> queryFunc,
Function<T, Long> keyExtractor
) {
if (CollectionUtils.isEmpty(ids)) {
return Collections.emptyMap();
}
return queryFunc.apply(ids).stream().collect(Collectors.toMap(keyExtractor, Function.identity(), (a, b) -> a));
}
}
4.结果

2.2 场景2
