别再写原生SQL了!MyBatis-Plus QueryWrapper的selectMaps()方法,搞定复杂统计查询

张开发
2026/4/20 18:03:21 15 分钟阅读

分享文章

别再写原生SQL了!MyBatis-Plus QueryWrapper的selectMaps()方法,搞定复杂统计查询
告别原生SQLMyBatis-Plus QueryWrapper的selectMaps()实战指南在Java后端开发中数据统计和报表功能几乎是每个系统都绕不开的需求。传统做法往往是编写冗长的原生SQL然后在DAO层进行结果映射。这种模式不仅代码臃肿维护困难还容易因为字符串拼接导致SQL注入风险。今天我要分享的是MyBatis-Plus中一个被严重低估的特性——selectMaps()方法它能让你用面向对象的方式处理复杂统计查询彻底告别原生SQL的烦恼。1. 为什么需要selectMaps()想象一下这样的场景产品经理要求你开发一个用户画像统计功能需要按性别分组统计用户数量并且要将数据库中的数字编码如1代表男0代表女转换为前端可读的文字标签。传统做法可能会这样写Select(SELECT sex, COUNT(id) AS num FROM user GROUP BY sex) ListMapString, Object countUsersBySex();这种方式有几个明显痛点SQL以字符串形式硬编码在注解或XML中结果集需要手动处理类型转换修改查询条件时需要同步修改多个地方的SQL而selectMaps()配合QueryWrapper的链式调用可以完美解决这些问题。它最核心的价值在于类型安全通过Lambda表达式引用实体属性避免字段名拼写错误可组合性查询条件可以动态拼接复用性极强直接映射返回的ListMap结构天然适合前端展示2. selectMaps()基础用法让我们从一个最简单的例子开始。假设要统计不同状态下的订单数量LambdaQueryWrapperOrder wrapper Wrappers.lambdaQuery(); wrapper.select(status, COUNT(id) AS count) .groupBy(Order::getStatus); ListMapString, Object result orderMapper.selectMaps(wrapper);这段代码等效于SQLSELECT status, COUNT(id) AS count FROM order GROUP BY status返回的数据结构是这样的[ {status: 1, count: 152}, {status: 2, count: 87}, {status: 3, count: 43} ]几个关键点需要注意select()方法可以直接传入SQL片段用于指定查询字段聚合函数如COUNT、SUM等需要指定别名AS语法返回的Map中key是查询字段名或别名value是数据库原始值3. 处理复杂统计场景3.1 条件判断与字段转换实际业务中经常需要在查询时做条件判断。比如用户性别在数据库存的是数字但需要在前端显示为文字LambdaQueryWrapperUser wrapper Wrappers.lambdaQuery(); wrapper.select( sex, COUNT(id) AS num, CASE WHEN sex 1 THEN 男 WHEN sex 0 THEN 女 ELSE 未知 END AS sexLabel ).groupBy(User::getSex); ListMapString, Object result userMapper.selectMaps(wrapper);等效SQLSELECT sex, COUNT(id) AS num, CASE WHEN sex 1 THEN 男 WHEN sex 0 THEN 女 ELSE 未知 END AS sexLabel FROM user GROUP BY sex提示CASE WHEN语句在统计报表中非常实用可以用来实现数据分类、状态映射等复杂逻辑。3.2 多维度分组统计对于需要按多个字段分组的场景比如统计每个部门下不同职级的员工数量LambdaQueryWrapperEmployee wrapper Wrappers.lambdaQuery(); wrapper.select( department_id, job_level, COUNT(id) AS headcount ).groupBy( Employee::getDepartmentId, Employee::getJobLevel ); ListMapString, Object result employeeMapper.selectMaps(wrapper);返回结果示例[ {department_id: 101, job_level: 3, headcount: 12}, {department_id: 101, job_level: 4, headcount: 5}, {department_id: 102, job_level: 3, headcount: 8} ]3.3 时间维度统计处理时间数据是统计分析的常见需求。比如要统计每日新增用户数LambdaQueryWrapperUser wrapper Wrappers.lambdaQuery(); wrapper.select( DATE(create_time) AS date, COUNT(id) AS new_users ).groupBy(DATE(create_time)) .orderByAsc(DATE(create_time)); ListMapString, Object result userMapper.selectMaps(wrapper);这里用到了MySQL的DATE函数来提取日期部分其他数据库也有类似的函数如Oracle的TRUNC、PostgreSQL的DATE_TRUNC等。4. 高级技巧与性能优化4.1 动态字段选择有时我们需要根据条件动态选择查询字段。QueryWrapper的select方法支持Lambda表达式可以优雅地实现这一点LambdaQueryWrapperUser wrapper Wrappers.lambdaQuery(); boolean needDetail true; // 从参数获取 if (needDetail) { wrapper.select( User::getId, User::getName, User::getAge ); } else { wrapper.select( User::getId, User::getName ); }4.2 分页统计查询对于大数据量的统计分页是必须的。MyBatis-Plus的分页接口同样支持selectMapsLambdaQueryWrapperOrder wrapper Wrappers.lambdaQuery(); wrapper.select( product_id, SUM(amount) AS total_amount ).groupBy(Order::getProductId); PageMapString, Object page new Page(1, 10); IPageMapString, Object result orderMapper.selectMapsPage(page, wrapper);返回的IPage对象包含分页信息和数据列表非常适合前后端分离的场景。4.3 使用SQL函数QueryWrapper的apply方法允许在条件中使用数据库函数。比如IP地址范围查询wrapper.apply(INET_ATON(ip_address) BETWEEN INET_ATON({0}) AND INET_ATON({1}), startIp, endIp);5. 实战案例电商数据看板让我们通过一个完整的电商数据统计案例展示selectMaps()在实际项目中的应用。假设需要开发一个数据看板包含以下指标当日订单总数和总金额按支付方式分组的订单数热销商品TOP 10// 当日订单统计 LambdaQueryWrapperOrder dailyWrapper Wrappers.lambdaQuery(); dailyWrapper.select( COUNT(id) AS order_count, SUM(amount) AS total_amount ).apply(DATE(create_time) CURRENT_DATE); MapString, Object dailyStats orderMapper.selectMaps(dailyWrapper).get(0); // 支付方式分布 LambdaQueryWrapperOrder paymentWrapper Wrappers.lambdaQuery(); paymentWrapper.select( payment_method, COUNT(id) AS count ).groupBy(Order::getPaymentMethod); ListMapString, Object paymentStats orderMapper.selectMaps(paymentWrapper); // 热销商品 LambdaQueryWrapperOrderItem productWrapper Wrappers.lambdaQuery(); productWrapper.select( product_id, product_name, SUM(quantity) AS sales_volume ).groupBy(OrderItem::getProductId) .orderByDesc(sales_volume) .last(LIMIT 10); ListMapString, Object hotProducts orderItemMapper.selectMaps(productWrapper);这个案例展示了如何用selectMaps()快速实现典型的数据看板需求所有查询都通过链式调用完成没有一行原生SQL。

更多文章