欢迎光临
我们一直在努力

⚡SimpleDAO企业实战教程(02)- 联表分页

相关开源地址:

  • 核心框架源码:gitee.com/gao_zhenzhong/simple-dao
  • 系统底座:gitee.com/gao_zhenzhong/simple-dao-starter
  • 代码生成器:gitee.com/gao_zhenzhong/simple-dao-coder
  • 实战案例:gitee.com/gao_zhenzhong/simple-dao-demo
  • 系列教程:从零到生产落地SimpleDAO,本集聚焦联表查询+分页场景,全程原生SQL、零XML、高性能,仅需Java基础即可上手。

    📋 前置知识

    本教程无需复杂框架经验,你只需要掌握:

    ✅ 必备基础❌ 无需掌握
    Java(类、接口、注解、泛型) MyBatis/Hibernate 任何知识
    SQL(SELECT、JOIN、WHERE) XML配置、动态标签、插件开发
    Spring Boot(数据源配置、依赖注入) Spring Boot自动配置原理
    IDE运行Maven项目 Maven高级配置、父子工程

    📚 全套教程总览

    集数 · 标题时长核心内容
    01 · 单表 CRUD + 审计 + 逻辑删除 约 6 min 零代码单表操作、自动填充审计、内置逻辑删除
    02 · 联表查询 + 分页 约 5 min 单/多表统一API,无需ResultMap
    03 · 条件进阶:IN + 子查询 约 5 min 替代MyBatis foreach标签,动态片段拼接
    04 · 多表联查 + 复杂条件 约 5 min 多表关联、区间筛选通用写法
    05 · 报表聚合:GROUP BY + 聚合函数 约 6 min 原生SQL报表无限制,不受ORM束缚
    06 · mergeParams 多组条件合并 约 6 min 复杂报表拆分多条件,解耦复用
    07 · 多租户 + 数据权限 · AOP 破局 约 7 min 不用MyBatis拦截器,Spring原生AOP扩展
    08 · 脱敏 + 审计扩展 · 框架不设限 约 7 min 纠正MyBatis持久层脱敏错误分层思路

    🎯 本集核心目标

  • 订单表(bus_order)关联用户表(sys_user)实现联表分页查询
  • 掌握add方法实现跨表条件透传
  • 理解SimpleDAO高性能COUNT计数原理
  • 对比传统框架,体会SQL-First范式的简洁性
  • 📦 项目基础准备

    1. 项目结构

    标准Spring Boot工程结构,核心文件如下:

    demo02_join_query/
    ├── pom.xml # 依赖配置
    └── src/main/
    ├── java/example/
    │ ├── DemoApplication.java # 启动类
    │ └── order/ # 订单业务模块
    │ ├── Order.java # 订单实体类
    │ ├── OrderVO.java # 联表结果VO
    │ ├── OrderCond.java # 条件类
    │ └── OrderDao.java # DAO层
    └── resources/
    ├── application.yml # 数据源配置
    └── schema.sql # 建表&测试数据

    2. 核心依赖

    仅需4个核心依赖,无冗余依赖,开箱即用:

    <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>

    • spring-boot-starter-jdbc:Spring JDBC基础能力
    • simple-dao:核心框架,提供CRUD、分页、条件拼接能力
    • h2:内存数据库,无需额外部署,测试便捷
    • lombok:简化实体类编写(Getter/Setter/ToString等)

    3. 数据源配置(application.yml)

    标准Spring Boot数据源配置,替换URL/driver即可适配MySQL/Oracle等:

    spring:
    datasource:
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=–1 # H2内存库
    driver-class-name: org.h2.Driver
    username: sa
    password:
    sql:
    init:
    schema-locations: classpath:schema.sql # 启动时执行建表SQL
    mode: always

    4. 建表&测试数据(schema.sql)

    (1)表结构定义

    用户表+订单表,包含业务字段+审计字段(创建时间、逻辑删除等):

    — 用户表(sys_user)
    CREATE TABLE IF NOT EXISTS sys_user (
    id INTEGER PRIMARY KEY,
    name VARCHAR(50) NOT NULL, — 用户名
    age INTEGER, — 年龄
    email VARCHAR(100) UNIQUE NOT NULL, — 邮箱
    phone VARCHAR(20) UNIQUE, — 手机号
    password VARCHAR(64) NOT NULL, — 密码
    sex CHAR(1), — 性别 1男 2女
    status INTEGER DEFAULT 1, — 状态 1正常 0禁用
    create_time TIMESTAMP,
    create_by INTEGER,
    update_time TIMESTAMP,
    update_by INTEGER,
    dr INTEGER DEFAULT 0 — 逻辑删除标记
    );

    — 订单表(bus_order)
    CREATE TABLE IF NOT EXISTS bus_order (
    id INTEGER PRIMARY KEY,
    order_no VARCHAR(50) UNIQUE NOT NULL, — 订单编号
    user_id INTEGER NOT NULL, — 关联用户ID
    total_amount DECIMAL(12,2) NOT NULL, — 订单总金额
    order_status INTEGER DEFAULT 0, — 订单状态 0待支付 1已支付
    create_time TIMESTAMP,
    create_by INTEGER,
    update_time TIMESTAMP,
    update_by INTEGER,
    dr INTEGER DEFAULT 0 — 逻辑删除标记
    );

    (2)测试数据

    插入3个用户+4个订单,用于联表查询测试:

    — 插入用户数据
    INSERT INTO sys_user (id, name, age, email, phone, password, sex, status) VALUES
    (1, '张三', 25, 'zhangsan@test.com', '13800138001', 'e10adc3949ba59abbe56e057f20f883e', 1, 1),
    (2, '李四', 30, 'lisi@test.com', '13800138002', 'e10adc3949ba59abbe56e057f20f883e', 1, 1),
    (3, '王五', 28, 'wangwu@test.com', '13800138003', 'e10adc3949ba59abbe56e057f20f883e', 2, 1);

    — 插入订单数据
    INSERT INTO bus_order (id, order_no, user_id, total_amount, order_status) VALUES
    (1, 'ORD2026001', 1, 1999.00, 1),
    (2, 'ORD2026002', 2, 299.00, 1),
    (3, 'ORD2026003', 1, 399.00, 1),
    (4, 'ORD2026004', 1, 1299.00, 1);

    👨💻 核心代码实现

    1. 实体类:Order(订单表映射)

    用@Table指定数据库表名,@Id标记主键,审计字段可通过@Schema(hidden=true)隐藏:

    @Data
    @Schema(description = "【订单表】实体")
    @Table("bus_order") // 关联数据库表名
    public class Order {
    @Id // 主键标记
    @Schema(description = "主键ID")
    private Integer id;

    @Schema(description = "订单编号")
    private String orderNo;

    @Schema(description = "用户ID")
    private Integer userId;

    // 省略其他业务字段(totalAmount、orderStatus等)

    // 审计字段(隐藏,无需前端展示)
    @Schema(hidden = true)
    private LocalDateTime createTime;
    @Schema(hidden = true)
    private Integer createBy;
    @Schema(hidden = true)
    private Integer dr; // 逻辑删除标记
    }

    2. 联表结果VO:OrderVO

    继承Order实体,扩展用户表字段(联表查询结果映射):

    @Getter
    @Setter
    @ToString(callSuper = true) // 打印时包含父类字段
    public class OrderVO extends Order {
    @Schema(description = "用户姓名")
    private String userName; // 对应sys_user.name

    @Schema(description = "用户手机号")
    private String userPhone; // 对应sys_user.phone

    @Schema(description = "用户邮箱")
    private String userEmail; // 对应sys_user.email
    }

    说明:VO字段名(如userName)需与联表SQL中的别名(u.name user_name)对应,框架自动完成字段映射。

    3. DAO层:OrderDao(联表分页核心)

    核心逻辑:原生LEFT JOIN SQL + page方法一键分页

    @Repository
    public class OrderDao extends BaseDao<Order> {
    // 联表SQL:订单表LEFT JOIN用户表,查询订单+用户字段
    private final static String SQL = """
    SELECT t.id, t.order_no, t.user_id, t.total_amount, t.order_status,
    t.create_time, t.dr,
    u.name user_name, u.phone user_phone, u.email user_email
    FROM bus_order t
    LEFT JOIN sys_user u ON t.user_id = u.id — 关联用户表
    """
    ;

    /**
    * 订单+用户联表分页查询
    * @param cond 查询条件
    * @return 分页结果(OrderVO)
    */

    public Page<OrderVO> pageJoin(OrderCond cond) {
    // page方法:自动拼接条件、生成COUNT、分页LIMIT
    return page(SQL, cond, OrderVO.class);
    }
    }

    关键说明:

  • SQL完全原生,LEFT JOIN/INNER JOIN由业务决定,框架不做黑盒处理;
  • page方法参数:原生SQL + 条件对象 + VO类型,一键返回分页结果;
  • 支持任意多表联查,只需修改SQL,代码无需额外调整。
  • 4. 条件类:OrderCond(跨表条件透传)

    核心是addCondition方法,通过add方法拼接跨表条件,一行搞定:

    @Data
    public class OrderCond extends BaseCondition {
    // 查询条件字段(支持订单表+用户表字段)
    private String orderNo; // 订单编号
    private Integer userId; // 订单-用户ID
    private String userName; // 用户姓名
    private String userPhone; // 用户手机号
    private String[] orderNos; // 订单编号数组(IN查询)

    @Override
    protected void addCondition() {
    // 1. 订单编号模糊查询(%xxx%),第三个参数3表示前后模糊匹配
    add("AND t.order_no LIKE ?", orderNo, 3);
    // 2. 订单-用户ID精准匹配
    add("AND t.user_id = ?", userId);
    // 3. 用户名反向模糊查询(NOT LIKE %xxx%)
    add("AND u.name NOT LIKE ?", userName, 3);
    // 4. 用户手机号精准匹配
    add("AND u.phone = ?", userPhone);
    // 5. 订单编号IN查询(数组自动展开为 (?, ?, …))
    add("AND t.order_no IN ", orderNos);
    }
    }

    add方法核心规则:

    • 第一个参数:SQL条件片段(支持任意跨表别名,如t./u.);
    • 第二个参数:条件值(null时自动忽略该条件,无需手动判断);
    • 第三个参数(可选):模糊匹配类型(1:前模糊 %xxx;2:后模糊 xxx%;3:前后模糊 %xxx%);
    • IN查询:只需写AND 字段 IN ,数组参数自动展开为IN (?, ?, …)。

    🚀 业务层调用&效果验证

    1. 调用示例

    @Service
    public class OrderService {
    @Autowired
    private OrderDao orderDao;

    public void demoJoinPage() {
    // 构建查询条件:用户名含“张” + 订单编号IN指定值
    OrderCond cond = OrderCond.builder()
    .userName("张")
    .orderNos(new String[]{"ORD2026001", "ORD2026003", "ORD2026004"})
    .build();

    // 联表分页查询(默认页码1,每页10条)
    Page<OrderVO> page = orderDao.pageJoin(cond);

    // 打印结果
    log.info("总记录数: {}", page.getRowCount());
    page.getDataList().forEach(vo -> log.info("订单+用户信息: {}", vo));
    }
    }

    2. 框架自动生成的SQL(日志)

    — 高性能COUNT语句(替换法,非子查询)
    [INFO] SELECT COUNT(1) FROM bus_order t LEFT JOIN sys_user u ON t.user_id = u.id WHERE u.name LIKE '%张%' AND t.order_no IN ('ORD2026001', 'ORD2026003', 'ORD2026004')

    — 分页查询语句(自动拼接LIMIT)
    [INFO] SELECT t.id, t.order_no, …, u.name user_name, …
    FROM bus_order t LEFT JOIN sys_user u ON t.user_id = u.id
    WHERE u.name LIKE '%张%' AND t.order_no IN ('ORD2026001', 'ORD2026003', 'ORD2026004')
    LIMIT 0,10

    高性能COUNT关键:框架并非将原SQL包裹为SELECT COUNT(1) FROM (原SQL),而是直接替换SELECT … FROM为SELECT COUNT(1) FROM,避免生成中间结果集,索引直接计数,高并发场景性能提升显著。

    🥊 MyBatis 传统写法 VS SimpleDAO 写法清晰对比

    对比1:模糊查询(用户名LIKE)

    MyBatis XML 写法

    <if test="userName != null">
    AND u.name LIKE CONCAT('%', #{userName}, '%')
    </if>

    说明:需要切换XML语法、OGNL判断、SQL函数三种写法,代码冗长,多文件跳转。

    SimpleDAO 写法

    add("AND u.name LIKE ?", userName, 3);

    说明:纯Java一行代码,数字3代表前后模糊匹配,参数为空自动忽略,无需判空。


    对比2:IN 多条件批量匹配

    MyBatis XML 写法

    <if test="orderNos != null and orderNos.size() > 0">
    AND t.order_no IN
    <foreach collection="orderNos" item="no" open="(" separator="," close=")">
    #{no}
    </foreach>
    </if>

    说明:多层标签嵌套,标签属性多,漏写任一属性直接报错,维护成本高。

    SimpleDAO 写法

    add("AND t.order_no IN ", orderNos);

    说明:直接传入数组,框架自动生成占位符,无需循环标签。当前文件内容过长,豆包只阅读了前 93%。

    📝 本集总结

  • 联表SQL:原生LEFT JOIN,完整可见,业务自主控制关联类型;
  • 条件拼接:add方法支持跨表条件透传,null自动忽略,无需手动判断;
  • 高性能分页:COUNT采用替换法,而非子查询,索引友好;
  • 零黑盒:全程SQL-First,无XML、无动态标签,代码即SQL;
  • 兼容性:与MyBatis完美共生,可渐进式替换原有DAO层。
  • 🎬 下集预告

    条件进阶:IN + 子查询,掌握addIn方法和条件类复用技巧,进一步简化复杂查询场景。

    源码获取:置顶链接(克隆即跑,零配置)。如果本教程对你有帮助,欢迎点赞+收藏,持续关注SimpleDAO企业实战系列!

    赞(0)
    未经允许不得转载:171主机测评 » ⚡SimpleDAO企业实战教程(02)- 联表分页
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址