⚡SimpleDAO 企业实战教程(06) mergeParams 多组条件合并

📅 2026/6/29 20:22:22
⚡SimpleDAO 企业实战教程(06) mergeParams 多组条件合并
⚡ SQL‑First 范式 · Java 版 SimpleDAO 企业实战教程 · 第 06 集 · mergeParams 多组条件合并相关开源地址核心框架源码https://gitee.com/gao_zhenzhong/simple-dao系统底座https://gitee.com/gao_zhenzhong/simple-dao-starter代码生成器https://gitee.com/gao_zhenzhong/simple-dao-coder实战案例本集源码https://gitee.com/gao_zhenzhong/simple-dao-demo 前置知识✅ 你只需要会❌ 你完全不需要会的Java 基础类、接口、注解、泛型MyBatis / Hibernate 任何知识基础 SQLSELECT、JOIN、WHERE、子查询XML 配置、动态标签、插件开发Spring Boot 基础配置数据源配置、启动类Spring Boot 自动配置原理、条件注解会用 IDE 运行 Maven 项目Maven 高级配置、父子工程 全套教程总览1 小时从零到生产落地集数 · 标题本集目录时长01 · 单表 CRUD 审计 逻辑删除实体注解 · 空 DAO · 保存审计 · ID查询 · 分页 · 逻辑删除约 6 min02 · 联表查询 分页联表 SQL · VO定义 · 条件类 · page调用 · 高性能COUNT约 4 min03 · 条件进阶IN 子查询IN自动展开 · 子查询拼接 · add vs and · 三种动态边界约 6 min04 · 多表联查 复杂条件行锁 · updateNull · 重复性校验 · 三表联查透传 · 时间范围约 6 min05 · 报表聚合GROUP BY 聚合函数三表JOIN聚合 · 条件类复用 · 独立判空 · 日志控制到方法约 6 min06 · mergeParams 多组条件合并本集多条件类定义 · SQL多位置嵌入 · mergeParams合并 · 条件跨位置复用约 5 min07 · 多租户 数据权限 · AOP 破局传统痛点 · 构造器 add 租户ID · 数据权限 · 对比 MyBatis 插件约 7 min08 · 脱敏 审计扩展 · 框架不设限字段脱敏VO getter· 审计重写 · 逻辑删除调整约 7 min 项目快速上手本集案例依旧内置 H2 内存库无需安装任何外部数据库克隆项目直接启动即可运行。本集重点展示多组条件合并—— 当报表需要同时接收时间范围、业务筛选、数据权限等多组条件且这些条件需要嵌入 SQL 的不同位置时SimpleDAO 如何做到清晰又灵活。完整项目层级结构demo06_mergeParams/ ├── pom.xml └── src/main/ ├── java/example/ │ ├── DemoApplication.java // 启动类内置全套测试逻辑 │ └── report/ │ ├── TimeCond.java // 时间条件类 │ ├── BizCond.java // 业务筛选条件类 │ ├── ValidCond.java // 有效数据条件类 │ ├── ReportDao.java // 报表 DAO核心mergeParams │ └── ReportVo.java // 报表结果 VO └── resources/ ├── application.yml // 极简数据源配置 └── schema.sql // 三表建表 测试数据1. Maven 依赖pom.xml说明仅依赖 Spring JDBC SimpleDAO 核心包依赖体积缩减至 1/3。dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependencydependencygroupIdcom.simple/groupIdartifactIdsimple-dao/artifactId/dependencydependencygroupIdcom.h2database/groupIdartifactIdh2/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependency2. 配置文件application.yml说明无框架专属复杂配置仅标准 Spring 数据源。simple-dao.show-sql: false全局关闭 SQL 日志。spring:datasource:url:jdbc:h2:mem:testdb;DB_CLOSE_DELAY-1driver-class-name:org.h2.Driverusername:sapassword:sql:init:schema-locations:classpath:schema.sqlmode:alwayssimple-dao.show-sql:false3. 建表脚本 测试数据schema.sql说明包含用户表sys_user、订单表bus_order、商品表bus_goods一套典型的一对多对多关联。测试数据已预置开箱即用。-- 1. 用户表3条DROPTABLEIFEXISTSsys_user;CREATETABLEsys_user(idBIGINTPRIMARYKEY,nameVARCHAR(20)NOTNULL,ageINTNOTNULL,emailVARCHAR(50),drTINYINTDEFAULT0COMMENT删除标记 0-未删 1-已删);INSERTINTOsys_user(id,name,age,email)VALUES(1,张三,25,zhangsantest.com),(2,李四,30,lisitest.com),(3,王五,28,wangwutest.com);-- 2. 订单表4条DROPTABLEIFEXISTSbus_order;CREATETABLEbus_order(idBIGINTPRIMARYKEY,user_idBIGINTNOTNULLCOMMENT关联sys_user.id,order_noVARCHAR(30)NOTNULLCOMMENT订单号,create_timeDATETIMENOTNULLCOMMENT创建时间,create_byBIGINTNOTNULLCOMMENT创建人,update_timeDATETIMENOTNULLCOMMENT修改时间,update_byBIGINTNOTNULLCOMMENT修改人,drTINYINTDEFAULT0COMMENT删除标记 0-未删 1-已删,FOREIGNKEY(user_id)REFERENCESsys_user(id));INSERTINTObus_order(id,user_id,order_no,create_time,create_by,update_time,update_by)VALUES(1,1,ORD2026001,2026-02-01 10:00:00,1,2026-02-01 10:00:00,1),(2,1,ORD2026002,2026-02-02 14:30:00,1,2026-02-02 14:30:00,1),(3,2,ORD2026003,2026-02-03 09:15:00,2,2026-02-03 09:15:00,2),(4,3,ORD2026004,2026-02-04 16:20:00,3,2026-02-04 16:20:00,3);-- 3. 商品表3条DROPTABLEIFEXISTSbus_goods;CREATETABLEbus_goods(idBIGINTPRIMARYKEY,order_idBIGINTNOTNULLCOMMENT关联bus_order.id,goods_nameVARCHAR(50)NOTNULLCOMMENT商品名,priceDECIMAL(10,2)NOTNULLCOMMENT商品价格,drTINYINTDEFAULT0COMMENT删除标记 0-未删 1-已删,FOREIGNKEY(order_id)REFERENCESbus_order(id));INSERTINTObus_goods(id,order_id,goods_name,price)VALUES(1,1,手机,1999.00),(2,2,耳机,199.00),(3,3,键盘,299.00); 核心业务代码演示第一层实体类Goods.java说明Table绑定表名Id标记主键。本集重点在多组条件合并实体类快速过。SetterGetterTable(bus_goods)publicclassGoods{IdprivateLongid;privateLongorderId;privateStringgoodsName;privateBigDecimalprice;privateBytedr;}第二层报表 VOReportVo.java说明按商品分组统计的结果包含商品名、订单数、商品数、总金额、均价。DatapublicclassReportVo{privateStringgoodsName;// 商品名privateIntegerorderCount;// 订单数privateIntegergoodsCount;// 商品数privateBigDecimaltotalAmount;// 总金额privateBigDecimalavgPrice;// 均价}第三层条件类本集核心多条件类各司其职说明我们定义了三个独立的条件类各司其职、互不干扰。每个条件类只关心自己那部分规则通过继承BaseCondition并在addCondition()中定义自己的条件逻辑。TimeCond.java—— 时间范围条件SetterGetterpublicclassTimeCondextendsBaseCondition{privateLocalDateTimeorderTimeStart;// 开始时间privateLocalDateTimeorderTimeEnd;// 结束时间OverrideprotectedvoidaddCondition(){add(AND create_time ?,orderTimeStart);add(AND create_time ?,orderTimeEnd);}}BizCond.java—— 业务筛选条件SetterGetterpublicclassBizCondextendsBaseCondition{privateStringgoodsName;// 商品名模糊privateDoublepriceMin;// 最低价格privateDoublepriceMax;// 最高价格OverrideprotectedvoidaddCondition(){add(AND t.price ?,priceMin);add(AND t.price ?,priceMax);add(AND t.goods_name LIKE ?,goodsName,3);}}ValidCond.java—— 有效数据条件SetterGetterpublicclassValidCondextendsBaseCondition{privateStringuserName;// 用户名模糊OverrideprotectedvoidaddCondition(){add(AND name LIKE ?,userName,3);add(AND dr 0);// 固定过滤}}第四层DAO 层ReportDao.java核心mergeParams说明需求是「按商品分组统计但筛选条件分别来自三张表且要嵌入 SQL 的不同位置」时间条件→ 嵌入订单子查询timeCond.and()业务条件→ 作用于主查询 WHEREbizCond.and()有效用户条件→ 嵌入用户子查询validCond.where()三个条件片段被精确地放置在了它们该去的地方。最后通过mergeParams()按顺序合并参数列表。RepositorypublicclassReportDaoextendsBaseDaoGoods{/** * 多条件合并报表统计 * 条件分别嵌入 SQL 的 3 个不同位置 */publicListReportVoreportGoodsByMerge(TimeCondtimeCond,BizCondbizCond,ValidCondvalidCond){StringsqlSELECT t.goods_name, COUNT(o.id) order_count, COUNT(t.id) goods_count, SUM(t.price) total_amount, AVG(t.price) avg_price FROM bus_goods t JOIN (SELECT id, user_id FROM bus_order WHERE dr0 timeCond.and()) o ON t.order_id o.id WHERE t.dr 0 bizCond.and()AND o.user_id IN (SELECT id FROM sys_user validCond.where()) GROUP BY t.goods_name;// 按顺序合并三个条件类的参数数组returnlist(sql,ReportVo.class,BaseCondition.mergeParams(timeCond,bizCond,validCond));}}mergeParams两大核心作用作用说明示例合并参数按传入顺序把多个条件类的参数列表合并成一个完整数组永不乱序mergeParams(timeCond, bizCond, validCond)条件复用同一个条件类可在 SQL 不同位置多次出现参数同样可以重复传入下文的 UNION ALL 示例 条件复用典型场景UNION ALL当你在 UNION ALL 中需要对多个独立查询应用完全相同的时间条件时mergeParams配合条件复用可以避免重复构造-- 纯 SQL两段查询共用同一个时间范围SELECT收入AStype,SUM(amount)FROMt_incomeWHEREcreate_time?ANDcreate_time?UNIONALLSELECT退费AStype,SUM(amount)FROMt_refundWHEREcreate_time?ANDcreate_time?// Java 代码同一个 dateCond 在 SQL 中出现两次mergeParams 也传两次StringsqlSELECT 收入 AS type, SUM(amount) FROM t_income WHERE 11 dateCond.and() UNION ALL SELECT 退费 AS type, SUM(amount) FROM t_refund WHERE 11 dateCond.and();returnlist(sql,IncomeVO.class,BaseCondition.mergeParams(dateCond,dateCond));要点条件类没有被绑定在某个固定的 SQL 片段上它是一个完全独立的、可携带的参数单元。你可以在任何需要它的地方调用.and()或.where()然后由mergeParams按指定顺序统一调度这些单元产生的参数。将来要拿掉或新增某个条件只需修改一处其他位置完全不用动。 业务调用与运行日志场景一全条件组合调用代码传入时间范围 业务筛选商品名模糊 价格下限 有效用户用户名模糊。LocalDateTimestartLocalDateTime.of(2026,2,1,0,0,0);LocalDateTimeendLocalDateTime.of(2026,2,4,23,59,59);TimeCondtimeCondTimeCond.builder().orderTimeStart(start).orderTimeEnd(end).build();BizCondbizCondBizCond.builder().goodsName(手).priceMin(1000.0).build();ValidCondvalidCondValidCond.builder().userName(张).build();reportDao.reportGoodsByMerge(timeCond,bizCond,validCond).forEach(i-log.info(结果{},i));运行日志[INFO] SELECT t.goods_name, COUNT(o.id) order_count, COUNT(t.id) goods_count, SUM(t.price) total_amount, AVG(t.price) avg_price FROM bus_goods t JOIN (SELECT id, user_id FROM bus_order WHERE dr0 AND create_time 2026-02-01 00:00:00 AND create_time 2026-02-04 23:59:59) o ON t.order_id o.id WHERE t.dr 0 AND t.price 1000.0 AND t.goods_name LIKE %手% AND o.user_id IN (SELECT id FROM sys_user WHERE name LIKE %张% AND dr0) GROUP BY t.goods_name [INFO] 结果ReportVo(goodsName手机, orderCount1, goodsCount1, totalAmount1999.00, avgPrice1999.00)场景二缺时间条件调用代码传入空的TimeCond时间条件被自动忽略。reportDao.reportGoodsByMerge(newTimeCond(),bizCond,validCond).forEach(i-log.info(结果{},i));运行日志[INFO] SELECT t.goods_name, COUNT(o.id) order_count, COUNT(t.id) goods_count, SUM(t.price) total_amount, AVG(t.price) avg_price FROM bus_goods t JOIN (SELECT id, user_id FROM bus_order WHERE dr0) o ON t.order_id o.id WHERE t.dr 0 AND t.price 1000.0 AND t.goods_name LIKE %手% AND o.user_id IN (SELECT id FROM sys_user WHERE name LIKE %张% AND dr0) GROUP BY t.goods_name [INFO] 结果ReportVo(goodsName手机, orderCount1, goodsCount1, totalAmount1999.00, avgPrice1999.00)关键观察日志中的 SQL完全没有时间范围条件—— 因为TimeCond为空对象add()方法检测到值为null自动忽略。你不需要写任何if (timeCond ! null)判断。 本集核心总结多条件类各司其职按业务维度拆分条件类时间、业务、权限每个类只管理自己的规则职责清晰互不干扰。SQL 多位置嵌入同一个查询中不同条件可以嵌入 SQL 的不同位置主查询 WHERE、子查询、JOIN 子句等通过.where()和.and()精准控制。mergeParams合并参数静态方法BaseCondition.mergeParams(cond1, cond2, ...)按顺序合并多个条件类的参数数组顺序完全由你控制永不乱序。条件跨位置复用同一个条件类可在 SQL 不同位置多次出现mergeParams同样支持重复传入特别适合 UNION ALL 等场景。零 XMLSQL 即所见全程无 XML、无标签、无 OGNL。你手写的 SQL 片段 条件类生成的片段 最终执行的 SQL完全透明。条件类 可组合的积木条件类没有被绑定在某个固定的 SQL 片段上它是完全独立的、可携带的参数单元。将来要拿掉或新增某个条件只需修改一处其他位置完全不用动。下一集我们将进入企业级开发的另一个硬核战场 ——多租户 数据权限 · AOP 破局看 SimpleDAO 如何用 Spring 原生 AOP 优雅解决传统 MyBatis 拦截器的诸多痛点敬请期待