java中批量查询+stream返回map提高查询效率

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

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

相关推荐
2301_780789662 小时前
跨平台适配!SDK 游戏盾兼容全终端游戏场景
java·网络·ddos
第九序言T2 小时前
Python 数据处理进阶(十八)批量处理同结构文件:一个文件夹一次跑完
开发语言·python·自动化·pandas·数据处理
Java后端的Ai之路2 小时前
CAS并发控制-从CPU指令到企业级乐观锁
vue.js·人工智能·python·cas·乐观锁
qq_426003962 小时前
UI自动化高频次登录login限流解决方案
python·ui·自动化
Sakura_j_h_3 小时前
EasyEDA PCB 快速设计Skills
c++·python·github
学长毕设3 小时前
基于springboot的食品仓库管理系统
java·spring boot·后端
步行cgn3 小时前
FormHttpMessageConverter 详解:Spring MVC 中表单数据的读写转换器
java·spring·mvc
龙亘川4 小时前
构建城市公共安全数字底座:亘川智城智慧安防系统全维度解读
java·大数据·开发语言·人工智能·信息可视化
金銀銅鐵4 小时前
[Java] 用 CLI 展示 EnumMap 增删键值对时的内部变化
java·源码·ai编程
quantdash_cc4 小时前
如何验证历史行情的交易日期是否完整覆盖研究区间?
python·数据分析·pandas·量化交易·股票数据·quantdash