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

⚡ SQL‑First 范式 · Java 版

🎬 SimpleDAO 企业实战教程 · 第 06 集 · mergeParams 多组条件合并

相关开源地址

  1. 核心框架源码:https://gitee.com/gao_zhenzhong/simple-dao
  2. 系统底座:https://gitee.com/gao_zhenzhong/simple-dao-starter
  3. 代码生成器:https://gitee.com/gao_zhenzhong/simple-dao-coder
  4. 实战案例(本集源码):https://gitee.com/gao_zhenzhong/simple-dao-demo

📋 前置知识

✅ 你只需要会❌ 你完全不需要会的
Java 基础(类、接口、注解、泛型)MyBatis / Hibernate 任何知识
基础 SQL(SELECT、JOIN、WHERE、子查询)XML 配置、动态标签、插件开发
Spring Boot 基础配置(数据源配置、启动类)Spring Boot 自动配置原理、条件注解
会用 IDE 运行 Maven 项目Maven 高级配置、父子工程

📚 全套教程总览(1 小时从零到生产落地)

集数 · 标题本集目录时长
01 · 单表 CRUD + 审计 + 逻辑删除实体注解 · 空 DAO · 保存审计 · ID查询 · 分页 · 逻辑删除约 6 min
02 · 联表查询 + 分页联表 SQL · VO定义 · 条件类 · page调用 · 高性能COUNT约 4 min
03 · 条件进阶:IN + 子查询IN自动展开 · 子查询拼接 · add vs and · 三种动态边界约 6 min
04 · 多表联查 + 复杂条件行锁 · updateNull · 重复性校验 · 三表联查透传 · 时间范围约 6 min
05 · 报表聚合:GROUP BY + 聚合函数三表JOIN+聚合 · 条件类复用 · 独立判空 · 日志控制到方法约 6 min
06 · mergeParams 多组条件合并(本集)多条件类定义 · SQL多位置嵌入 · mergeParams合并 · 条件跨位置复用约 5 min
07 · 多租户 + 数据权限 · AOP 破局传统痛点 · 构造器 add 租户ID · 数据权限 · 对比 MyBatis 插件约 7 min
08 · 脱敏 + 审计扩展 · 框架不设限字段脱敏(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。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.simple</groupId>
    <artifactId>simple-dao</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

2. 配置文件 application.yml

说明:无框架专属复杂配置,仅标准 Spring 数据源。simple-dao.show-sql: false 全局关闭 SQL 日志。

spring:
  datasource:
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
    driver-class-name: org.h2.Driver
    username: sa
    password:
  sql:
    init:
      schema-locations: classpath:schema.sql
      mode: always

simple-dao.show-sql: false

3. 建表脚本 + 测试数据 schema.sql

说明:包含用户表 sys_user、订单表 bus_order、商品表 bus_goods,一套典型的一对多对多关联。测试数据已预置,开箱即用。

-- 1. 用户表(3条)
DROP TABLE IF EXISTS sys_user;
CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY,
    name VARCHAR(20) NOT NULL,
    age INT NOT NULL,
    email VARCHAR(50),
    dr TINYINT DEFAULT 0 COMMENT '删除标记 0-未删 1-已删'
);
INSERT INTO sys_user (id, name, age, email) VALUES
(1, '张三', 25, 'zhangsan@test.com'),
(2, '李四', 30, 'lisi@test.com'),
(3, '王五', 28, 'wangwu@test.com');

-- 2. 订单表(4条)
DROP TABLE IF EXISTS bus_order;
CREATE TABLE bus_order (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL COMMENT '关联sys_user.id',
    order_no VARCHAR(30) NOT NULL COMMENT '订单号',
    create_time DATETIME NOT NULL COMMENT '创建时间',
    create_by BIGINT NOT NULL COMMENT '创建人',
    update_time DATETIME NOT NULL COMMENT '修改时间',
    update_by BIGINT NOT NULL COMMENT '修改人',
    dr TINYINT DEFAULT 0 COMMENT '删除标记 0-未删 1-已删',
    FOREIGN KEY (user_id) REFERENCES sys_user(id)
);
INSERT INTO bus_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条)
DROP TABLE IF EXISTS bus_goods;
CREATE TABLE bus_goods (
    id BIGINT PRIMARY KEY,
    order_id BIGINT NOT NULL COMMENT '关联bus_order.id',
    goods_name VARCHAR(50) NOT NULL COMMENT '商品名',
    price DECIMAL(10,2) NOT NULL COMMENT '商品价格',
    dr TINYINT DEFAULT 0 COMMENT '删除标记 0-未删 1-已删',
    FOREIGN KEY (order_id) REFERENCES bus_order(id)
);
INSERT INTO bus_goods (id, order_id, goods_name, price) VALUES
(1, 1, '手机', 1999.00),
(2, 2, '耳机', 199.00),
(3, 3, '键盘', 299.00);

🔧 核心业务代码演示

第一层:实体类 Goods.java

说明@Table 绑定表名,@Id 标记主键。本集重点在多组条件合并,实体类快速过。

@Setter
@Getter
@Table("bus_goods")
public class Goods {
    @Id
    private Long id;
    private Long orderId;
    private String goodsName;
    private BigDecimal price;
    private Byte dr;
}

第二层:报表 VO ReportVo.java

说明:按商品分组统计的结果,包含商品名、订单数、商品数、总金额、均价。

@Data
public class ReportVo {
    private String goodsName;      // 商品名
    private Integer orderCount;    // 订单数
    private Integer goodsCount;    // 商品数
    private BigDecimal totalAmount;// 总金额
    private BigDecimal avgPrice;   // 均价
}

第三层:条件类(本集核心:多条件类各司其职)

说明:我们定义了三个独立的条件类,各司其职、互不干扰。每个条件类只关心自己那部分规则,通过继承 BaseCondition 并在 addCondition() 中定义自己的条件逻辑。

TimeCond.java —— 时间范围条件
@Setter
@Getter
public class TimeCond extends BaseCondition {
    private LocalDateTime orderTimeStart; // 开始时间
    private LocalDateTime orderTimeEnd;   // 结束时间

    @Override
    protected void addCondition() {
        add("AND create_time >= ?", orderTimeStart);
        add("AND create_time <= ?", orderTimeEnd);
    }
}
BizCond.java —— 业务筛选条件
@Setter
@Getter
public class BizCond extends BaseCondition {
    private String goodsName; // 商品名(模糊)
    private Double priceMin;  // 最低价格
    private Double priceMax;  // 最高价格

    @Override
    protected void addCondition() {
        add("AND t.price >= ?", priceMin);
        add("AND t.price <= ?", priceMax);
        add("AND t.goods_name LIKE ?", goodsName, 3);
    }
}
ValidCond.java —— 有效数据条件
@Setter
@Getter
public class ValidCond extends BaseCondition {
    private String userName; // 用户名(模糊)

    @Override
    protected void addCondition() {
        add("AND name LIKE ?", userName, 3);
        add("AND dr = 0");   // 固定过滤
    }
}

第四层:DAO 层 ReportDao.java(核心:mergeParams)

说明:需求是「按商品分组统计,但筛选条件分别来自三张表,且要嵌入 SQL 的不同位置」:

  • 时间条件 → 嵌入订单子查询(timeCond.and()
  • 业务条件 → 作用于主查询 WHERE(bizCond.and()
  • 有效用户条件 → 嵌入用户子查询(validCond.where()

三个条件片段被精确地放置在了它们该去的地方。最后通过 mergeParams() 按顺序合并参数列表。

@Repository
public class ReportDao extends BaseDao<Goods> {

    /**
     * 多条件合并报表统计
     * 条件分别嵌入 SQL 的 3 个不同位置
     */
    public List<ReportVo> reportGoodsByMerge(TimeCond timeCond, BizCond bizCond, ValidCond validCond) {
        String sql =
            "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 dr=0 " + 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";

        // 按顺序合并三个条件类的参数数组
        return list(sql, ReportVo.class, BaseCondition.mergeParams(timeCond, bizCond, validCond));
    }
}
📌 mergeParams 两大核心作用
作用说明示例
合并参数按传入顺序把多个条件类的参数列表合并成一个完整数组,永不乱序mergeParams(timeCond, bizCond, validCond)
条件复用同一个条件类可在 SQL 不同位置多次出现,参数同样可以重复传入下文的 UNION ALL 示例
📌 条件复用典型场景:UNION ALL

当你在 UNION ALL 中需要对多个独立查询应用完全相同的时间条件时,mergeParams 配合条件复用可以避免重复构造:

-- 纯 SQL:两段查询共用同一个时间范围
SELECT '收入' AS type, SUM(amount) FROM t_income  WHERE create_time >= ? AND create_time <= ?
UNION ALL
SELECT '退费' AS type, SUM(amount) FROM t_refund WHERE create_time >= ? AND create_time <= ?
// Java 代码:同一个 dateCond 在 SQL 中出现两次,mergeParams 也传两次
String sql = "SELECT '收入' AS type, SUM(amount) FROM t_income WHERE 1=1 " + dateCond.and() +
             " UNION ALL " +
             "SELECT '退费' AS type, SUM(amount) FROM t_refund WHERE 1=1 " + dateCond.and();
return list(sql, IncomeVO.class, BaseCondition.mergeParams(dateCond, dateCond));

要点:条件类没有被绑定在某个固定的 SQL 片段上,它是一个完全独立的、可携带的参数单元。你可以在任何需要它的地方调用 .and().where(),然后由 mergeParams 按指定顺序统一调度这些单元产生的参数。将来要拿掉或新增某个条件,只需修改一处,其他位置完全不用动。


📝 业务调用与运行日志

场景一:全条件组合

调用代码:传入时间范围 + 业务筛选(商品名模糊 + 价格下限)+ 有效用户(用户名模糊)。

LocalDateTime start = LocalDateTime.of(2026, 2, 1, 0, 0, 0);
LocalDateTime end = LocalDateTime.of(2026, 2, 4, 23, 59, 59);

TimeCond timeCond = TimeCond.builder().orderTimeStart(start).orderTimeEnd(end).build();
BizCond bizCond = BizCond.builder().goodsName("手").priceMin(1000.0).build();
ValidCond validCond = ValidCond.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 dr=0
             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 dr=0)
       GROUP BY t.goods_name
[INFO] 结果:ReportVo(goodsName=手机, orderCount=1, goodsCount=1, totalAmount=1999.00, avgPrice=1999.00)

场景二:缺时间条件

调用代码:传入空的 TimeCond,时间条件被自动忽略。

reportDao.reportGoodsByMerge(new 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 dr=0) 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 dr=0)
       GROUP BY t.goods_name
[INFO] 结果:ReportVo(goodsName=手机, orderCount=1, goodsCount=1, totalAmount=1999.00, avgPrice=1999.00)

关键观察:日志中的 SQL 完全没有时间范围条件 —— 因为 TimeCond 为空对象,add() 方法检测到值为 null 自动忽略。你不需要写任何 if (timeCond != null) 判断。


📌 本集核心总结

  1. 多条件类各司其职:按业务维度拆分条件类(时间、业务、权限),每个类只管理自己的规则,职责清晰,互不干扰。

  2. SQL 多位置嵌入:同一个查询中,不同条件可以嵌入 SQL 的不同位置(主查询 WHERE、子查询、JOIN 子句等),通过 .where().and() 精准控制。

  3. mergeParams 合并参数:静态方法 BaseCondition.mergeParams(cond1, cond2, ...) 按顺序合并多个条件类的参数数组,顺序完全由你控制,永不乱序。

  4. 条件跨位置复用:同一个条件类可在 SQL 不同位置多次出现,mergeParams 同样支持重复传入,特别适合 UNION ALL 等场景。

  5. 零 XML,SQL 即所见:全程无 XML、无标签、无 OGNL。你手写的 SQL 片段 + 条件类生成的片段 = 最终执行的 SQL,完全透明。

  6. 条件类 = 可组合的积木:条件类没有被绑定在某个固定的 SQL 片段上,它是完全独立的、可携带的参数单元。将来要拿掉或新增某个条件,只需修改一处,其他位置完全不用动。

下一集,我们将进入企业级开发的另一个硬核战场 —— 多租户 + 数据权限 · AOP 破局,看 SimpleDAO 如何用 Spring 原生 AOP 优雅解决传统 MyBatis 拦截器的诸多痛点,敬请期待!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值