在使用 MyBatis-Plus 进行开发时,经常会遇到需要对查询数据按某个字段求和的需求,而 MyBatis-Plus 本身并不直接支持 SUM
函数。通常,开发者可能会选择在 XML 文件中编写 SQL 来解决这个问题,但本文将提供一种更便捷的方式,通过封装通用方法,直接在 Java 代码中调用实现求和查询。
- MyBatis-Plus:3.4.0
- Spring Boot:2.7.17
- MySQL:8.0
首先,我们需要定义一个实体类来表示查询结果。以下是 OutputInvoice
实体类的代码:
package com.zxh.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
// 实体类,用于表示查询结果
@Data
@TableName("output_invoice")
public class OutputInvoice {
//开票金额
private BigDecimal amount;
//税额
private BigDecimal tax;
//发票状态,1'已开票',2'已作废'
private String status;
//发票号码
private String invoiceNo;
//发票代码
private String invoiceCode;
}
接下来,我们需要创建对应的数据库表,并插入一些示例数据。以下是 SQL 代码:
-- 创建 output_invoice 表
CREATE TABLE output_invoice (
-- 开票金额,使用 DECIMAL 类型存储精确的数值,10 为总位数,2 为小数位数
amount DECIMAL(10, 2) COMMENT '开票金额',
-- 税额,使用 DECIMAL 类型存储精确的数值,10 为总位数,2 为小数位数
tax DECIMAL(10, 2) COMMENT '税额',
-- 发票状态,1 表示已开票,2 表示已作废
status VARCHAR(2) COMMENT '发票状态,1已开票,2已作废',
-- 发票号码,用于唯一标识一张发票
invoiceNo VARCHAR(20) COMMENT '发票号码',
-- 发票代码,用于标识发票的类别等信息
invoiceCode VARCHAR(20) COMMENT '发票代码'
) COMMENT '销项发票信息表';
-- 插入示例数据
INSERT INTO output_invoice (amount, tax, status, invoiceNo, invoiceCode)
VALUES
(1000.00, 130.00, '1', '12345678', '00000001'),
(2000.50, 260.07, '1', '87654321', '00000002'),
(500.20, 65.03, '2', '98765432', '00000003');
为了实现通用的求和查询功能,我们创建一个
QueryUtils
工具类,该类封装了
selectSumBySQL
方法,用于执行求和查询。以下是工具类的代码:

package com.zxh.util;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import static cn.hutool.core.collection.CollectionUtil.isNotEmpty;
/**
* 查询工具类,提供通用的求和查询方法
*/
@Slf4j
public class QueryUtils {
/**
* 执行按字段求和的查询
* @param lambdaQueryWrapper Lambda 查询条件包装器
* @param sumSQL 求和的 SQL 语句
* @param tClass 实体类的 Class 对象
* @param queryExecutor 查询执行器,用于执行查询操作
* @param <T> 实体类的泛型类型
* @return 查询结果的实体类对象
*/
public <T> T selectSumBySQL(LambdaQueryWrapper<T> lambdaQueryWrapper, String sumSQL, Class<T> tClass, Function<QueryWrapper<T>, List<Map<String, Object>>> queryExecutor) {
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
// 使用 sum 求和
queryWrapper.select(sumSQL);
// 获取 LambdaQueryWrapper 的 SQL 片段和参数映射
String sqlSegment = lambdaQueryWrapper.getCustomSqlSegment();
Map<String, Object> paramNameValuePairs = lambdaQueryWrapper.getParamNameValuePairs();
log.info("执行 sum 查询 lambdaQueryWrapper SQL: {}", sqlSegment);
log.info("执行 sum 查询 lambdaQueryWrapper 参数: {}", paramNameValuePairs);
// 拼接 SQL 片段
if (sqlSegment != null && !sqlSegment.isEmpty()) {
if (sqlSegment.trim().toLowerCase().startsWith("where")) {
sqlSegment = sqlSegment.substring(5).trim();
}
queryWrapper.last("WHERE " + sqlSegment);
}
// 直接使用 LambdaQueryWrapper 的参数映射
queryWrapper.getParamNameValuePairs().putAll(paramNameValuePairs);
// 打印最终的 QueryWrapper 信息
log.info("执行 sum 查询 QueryWrapper SQL: {}", queryWrapper.getCustomSqlSegment());
log.info("执行 sum 查询 QueryWrapper 参数: {}", queryWrapper.getParamNameValuePairs());
// 使用执行器进行查询
List<Map<String, Object>> mapList = queryExecutor.apply(queryWrapper);
if (isNotEmpty(mapList)) {
Map<String, Object> sumMap = mapList.get(0);
if (sumMap != null) {
try {
// 创建实体类的实例
T result = tClass.getDeclaredConstructor().newInstance();
// 遍历实体类的字段
for (Field field : tClass.getDeclaredFields()) {
field.setAccessible(true);
String fieldName = field.getName();
Object value = sumMap.get(fieldName);
if (value != null) {
if (field.getType() == BigDecimal.class) {
value = new BigDecimal(value.toString());
}
// 将查询结果设置到实体类的对应字段中
field.set(result, value);
}
}
return result;
} catch (Exception e) {
log.error("查询求和方法异常:", e);
throw new RuntimeException(e);
}
}
}
return null;
}
}

最后,我们编写一个测试方法来验证 QueryUtils
工具类的功能。以下是测试代码:

package com.zxh;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zxh.dao.OutputInvoiceMapper;
import com.zxh.entity.OutputInvoice;
import com.zxh.util.QueryUtils;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
@Slf4j
class SpringbootDemoApplicationTests {
@Resource
private OutputInvoiceMapper outputInvoiceMapper;
@Test
public void test() {
QueryUtils queryUtils = new QueryUtils();
LambdaQueryWrapper<OutputInvoice> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(OutputInvoice::getStatus, "1");
//汇总的sql
String sumSQL = "SUM(amount) as amount,SUM(tax) as tax";
// 调用 selectSumBySQL 方法
OutputInvoice sumResult = queryUtils.selectSumBySQL(
lambdaQueryWrapper,
sumSQL,
OutputInvoice.class,
outputInvoiceMapper::selectMaps);
// 验证结果
if (sumResult != null) {
System.out.println("查询结果: " + sumResult);
} else {
System.out.println("未查询到结果");
}
}
}

打印结果

由于 LambdaQueryWrapper
本身不支持 SUM
函数,我们借助 QueryWrapper
传入自定义的求和 SQL 语句。将两者结合的原因是,LambdaQueryWrapper
在构建查询条件时具有优势,只需指定对象的字段名即可。通过这种方式,我们可以在不使用 XML 文件的情况下,直接在 Java 代码中实现按字段求和的查询功能。
通过以上步骤,你可以方便地在 MyBatis-Plus 项目中实现按字段求和的查询需求,提高开发效率。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2023-02-22 Springboot整合shiro