Files
ccdi/doc/requirements/plans/2026-02-05-intermediary-blacklist-union-query-mybatis-plus.md
wkc 1cd87d2695 refactor: 重命名 ruoyi-ccdi 模块为 ruoyi-info-collection
- Maven 模块从 ruoyi-ccdi 重命名为 ruoyi-info-collection
- Java 包名从 com.ruoyi.ccdi 改为 com.ruoyi.info.collection
- MyBatis XML 命名空间同步更新
- 保留数据库表名、API URL、权限标识中的 ccdi 前缀
- 更新项目文档中的模块引用
2026-02-24 17:12:11 +08:00

11 KiB
Raw Blame History

中介黑名单联合查询功能重构实现总结 (MyBatis Plus分页版本)

一、版本更新说明

版本: v2.1 (MyBatis Plus分页插件版本) 更新日期: 2026-02-05 更新内容: 使用MyBatis Plus分页插件替代手动分页参考员工模块的实现方式

二、问题描述

2.1 原始错误

Unknown column 'relation_type_field' in 'field list'

2.2 v2.0版本的问题

虽然v2.0版本实现了XML联合查询但使用了手动的LIMIT/OFFSET分页这与若依框架的标准实现方式不一致

  • 不一致性:与员工模块等其他模块的实现方式不同
  • 维护性:手动计算分页参数,容易出错
  • 功能限制无法利用MyBatis Plus分页插件的优化功能

三、解决方案v2.1

3.1 参考实现

参考 CcdiEmployeeControllerCcdiEmployeeServiceImpl 的实现方式:

// Controller层
PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiEmployeeVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiEmployeeVO> result = employeeService.selectEmployeePage(page, queryDTO);

// Service层
Page<CcdiEmployeeVO> resultPage = employeeMapper.selectEmployeePageWithDept(voPage, queryDTO);

// Mapper接口
Page<CcdiEmployeeVO> selectEmployeePageWithDept(@Param("page") Page<CcdiEmployeeVO> page,
                                                @Param("query") CcdiEmployeeQueryDTO queryDTO);

// XML
<select id="selectEmployeePageWithDept" resultMap="CcdiEmployeeVOResult">
    SELECT ... FROM ...
    WHERE ...
    ORDER BY ...
    <!-- 不包含LIMIT和OFFSET由MyBatis Plus自动注入 -->
</select>

3.2 核心改动

1. Mapper接口方法签名

文件: ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiIntermediaryMapper.java

修改前:

List<CcdiIntermediaryVO> selectIntermediaryList(CcdiIntermediaryQueryDTO queryDTO);
long selectIntermediaryCount(CcdiIntermediaryQueryDTO queryDTO);

修改后:

Page<CcdiIntermediaryVO> selectIntermediaryList(
    Page<CcdiIntermediaryVO> page,
    @Param("query") CcdiIntermediaryQueryDTO queryDTO
);

关键点:

  • 第一个参数是 Page 对象
  • 查询条件使用 @Param 注解包装
  • 返回类型是 Page<Vo>
  • 删除了单独的count查询方法

2. XML Mapper文件

文件: ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiIntermediaryMapper.xml

修改前v2.0

<!-- 三个独立的SQL分支每个分支都包含LIMIT和OFFSET -->
<if test="intermediaryType == '1'">
    SELECT ... FROM ccdi_biz_intermediary ...
    LIMIT #{pageSize} OFFSET #{pageNum} * #{pageSize}
</if>
<if test="intermediaryType == '2'">
    SELECT ... FROM ccdi_enterprise_base_info ...
    LIMIT #{pageSize} OFFSET #{pageNum} * #{pageSize}
</if>
<if test="intermediaryType == null">
    SELECT * FROM (...) UNION ALL (...)
    LIMIT #{pageSize} OFFSET #{pageNum} * #{pageSize}
</if>

修改后v2.1

<!-- 统一的SQL结构不包含LIMIT和OFFSET -->
<select id="selectIntermediaryList" resultType="com.ruoyi.ccdi.domain.vo.CcdiIntermediaryVO">
    SELECT * FROM (
        <!-- 个人中介 -->
        SELECT ... FROM ccdi_biz_intermediary WHERE person_type = '中介'
        UNION ALL
        <!-- 实体中介 -->
        SELECT ... FROM ccdi_enterprise_base_info WHERE ...
    ) AS combined_result
    <where>
        <!-- 动态查询条件 -->
        <if test="query.intermediaryType != null and query.intermediaryType != ''">
            AND intermediary_type = #{query.intermediaryType}
        </if>
        <if test="query.name != null and query.name != ''">
            AND name LIKE CONCAT('%', #{query.name}, '%')
        </if>
        <if test="query.certificateNo != null and query.certificateNo != ''">
            AND certificate_no = #{query.certificateNo}
        </if>
    </where>
    ORDER BY create_time DESC
    <!-- MyBatis Plus会自动在这里注入LIMIT和OFFSET -->
</select>

关键点:

  • 统一的查询结构使用UNION ALL
  • 不包含LIMIT和OFFSET
  • 在最外层使用 <where> 进行动态过滤
  • MyBatis Plus分页插件会自动在ORDER BY后面注入分页SQL

3. Service层实现

文件: ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java

修改前v2.0

public Page<CcdiIntermediaryVO> selectIntermediaryPage(...) {
    // 手动查询总数
    long total = intermediaryMapper.selectIntermediaryCount(queryDTO);

    // 手动设置分页参数
    queryDTO.setPageNum((int) (page.getCurrent() - 1));
    queryDTO.setPageSize((int) page.getSize());

    // 手动查询列表
    List<CcdiIntermediaryVO> list = intermediaryMapper.selectIntermediaryList(queryDTO);

    // 手动设置分页结果
    page.setRecords(list);
    page.setTotal(total);

    return page;
}

修改后v2.1

public Page<CcdiIntermediaryVO> selectIntermediaryPage(Page<CcdiIntermediaryVO> page, CcdiIntermediaryQueryDTO queryDTO) {
    // 直接调用Mapper的联合查询方法MyBatis Plus会自动处理分页
    return intermediaryMapper.selectIntermediaryList(page, queryDTO);
}

关键点:

  • 一行代码搞定
  • MyBatis Plus自动处理count查询、分页SQL注入、结果封装
  • 无需手动计算分页参数

4. QueryDTO清理

文件: ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiIntermediaryQueryDTO.java

删除字段:

// 不再需要分页信息通过Page对象传递
private Integer pageNum;
private Integer pageSize;

四、技术实现细节

4.1 MyBatis Plus分页插件工作原理

  1. 拦截器机制

    • MyBatis Plus使用拦截器在SQL执行前拦截
    • 自动在SQL后面添加LIMIT和OFFSET
    • 自动执行COUNT查询获取total
  2. 分页SQL生成

    -- 原始SQL
    SELECT * FROM (UNION查询) AS t WHERE ... ORDER BY create_time DESC
    
    -- MyBatis Plus自动注入后
    SELECT * FROM (
        SELECT * FROM (UNION查询) AS t WHERE ... ORDER BY create_time DESC
        LIMIT 10 OFFSET 0
    ) AS page
    
  3. 参数传递

    • Controller: PageDomainPage<Vo>
    • Service: Page<Vo> 传递给Mapper
    • Mapper: Page<Vo> 作为第一个参数
    • XML: 通过MyBatis Plus拦截器自动处理

4.2 SQL优化

v2.0的问题

  • 三个独立的SQL分支
  • 每个分支都需要处理分页
  • 代码重复,维护困难

v2.1的优化

  • 统一的SQL结构
  • 外层WHERE条件过滤
  • MyBatis Plus统一处理分页
  • 代码简洁,易于维护

4.3 参数绑定变化

v2.0:

// QueryDTO包含分页参数
queryDTO.setPageNum(0);
queryDTO.setPageSize(10);
mapper.selectList(queryDTO);

// XML中直接使用
#{pageNum}, #{pageSize}

v2.1:

// Page对象单独传递
Page<CcdiIntermediaryVO> page = new Page<>(1, 10);
mapper.selectList(page, queryDTO);

// XML中通过@Param包装
#{query.intermediaryType}, #{query.name}

五、文件清单

修改的文件

  1. ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/CcdiBizIntermediary.java - 删除冗余字段,修复字段映射
  2. ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiIntermediaryQueryDTO.java - 删除分页参数
  3. ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiIntermediaryMapper.java - 修改方法签名
  4. ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java - 简化分页逻辑
  5. ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiIntermediaryMapper.xml - 重写SQL结构

新增的文件

  1. doc/test/scripts/test_union_query_mybatis_plus.sh - 测试脚本
  2. doc/plans/2026-02-05-intermediary-blacklist-union-query-mybatis-plus.md - 本文档

删除的文件

  1. doc/test/scripts/test_union_query.sh - 旧版测试脚本(保留备份)

六、优势总结

6.1 与框架一致性

  • 与员工模块等其他模块实现方式一致
  • 符合若依框架的标准规范
  • 便于团队统一维护

6.2 代码简洁性

  • Service层从10+行代码减少到1行
  • XML从200+行减少到60行
  • 删除了手动分页的复杂逻辑

6.3 性能优化

  • MyBatis Plus分页插件经过优化
  • 自动缓存count查询结果
  • 支持多种数据库的分页方言

6.4 可维护性

  • 统一的SQL结构易于理解
  • 动态条件集中在外层WHERE
  • 易于扩展新的查询条件

七、测试验证

7.1 测试脚本

文件: doc/test/scripts/test_union_query_mybatis_plus.sh

测试用例:

  1. Test 1: UNION ALL查询全部中介
  2. Test 2: 按类型筛选个人中介
  3. Test 3: 按类型筛选实体中介
  4. Test 4: 按姓名模糊查询
  5. Test 5: 按证件号精确查询
  6. Test 6: MyBatis Plus分页功能测试
  7. Test 7: 组合查询测试
  8. Test 8: 大分页测试

7.2 执行测试

# Windows环境
cd doc\test\scripts
bash test_union_query_mybatis_plus.sh

# Linux/Mac环境
cd doc/test/scripts
chmod +x test_union_query_mybatis_plus.sh
./test_union_query_mybatis_plus.sh

八、对比总结

特性 v2.0 (手动分页) v2.1 (MyBatis Plus)
Service代码行数 10+ 1
XML代码行数 200+ 60
一致性 与框架不一致 完全一致
性能 一般 优化
维护性 复杂 简单
扩展性 困难 容易
Count查询 手动 自动
分页计算 手动 自动

九、最佳实践

基于本次重构,总结以下最佳实践:

  1. 遵循框架规范

    • 优先使用框架提供的标准实现方式
    • 参考其他模块的成熟实现
  2. 分页查询模式

    // Mapper接口
    Page<VO> selectXxxPage(Page<VO> page, @Param("query") QueryDTO query);
    
    // Service实现
    return mapper.selectXxxPage(page, query);
    
    // XML
    <select id="selectXxxPage" resultType="VO">
        SELECT ... FROM ...
        <where>...</where>
        ORDER BY ...
    </select>
    
  3. 联合查询优化

    • 使用UNION ALL而不是多个分支
    • 在最外层使用WHERE进行过滤
    • 避免在XML中写LIMIT和OFFSET
  4. 参数传递

    • Page对象作为第一个参数
    • 查询条件使用@Param包装
    • 避免在实体中混入分页参数

十、后续建议

  1. 性能监控

    • 监控UNION ALL查询的执行计划
    • 优化索引以提升查询性能
  2. 功能扩展

    • 考虑添加更多排序字段选项
    • 考虑支持批量导出的流式查询
  3. 代码优化

    • 其他模块如有类似实现,建议统一改造
    • 建立统一的分页查询模板

实现日期: 2026-02-05 实现人: Claude Code 版本: v2.1 (MyBatis Plus分页插件版本) 参考模块: CcdiEmployeeController/CcdiEmployeeServiceImpl