- Maven 模块从 ruoyi-ccdi 重命名为 ruoyi-info-collection - Java 包名从 com.ruoyi.ccdi 改为 com.ruoyi.info.collection - MyBatis XML 命名空间同步更新 - 保留数据库表名、API URL、权限标识中的 ccdi 前缀 - 更新项目文档中的模块引用
11 KiB
中介黑名单联合查询功能重构实现总结 (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 参考实现
参考 CcdiEmployeeController 和 CcdiEmployeeServiceImpl 的实现方式:
// 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分页插件工作原理
-
拦截器机制
- MyBatis Plus使用拦截器在SQL执行前拦截
- 自动在SQL后面添加LIMIT和OFFSET
- 自动执行COUNT查询获取total
-
分页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 -
参数传递
- Controller:
PageDomain→Page<Vo> - Service:
Page<Vo>传递给Mapper - Mapper:
Page<Vo>作为第一个参数 - XML: 通过MyBatis Plus拦截器自动处理
- Controller:
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}
五、文件清单
修改的文件
ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/CcdiBizIntermediary.java- 删除冗余字段,修复字段映射ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiIntermediaryQueryDTO.java- 删除分页参数ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiIntermediaryMapper.java- 修改方法签名ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java- 简化分页逻辑ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiIntermediaryMapper.xml- 重写SQL结构
新增的文件
doc/test/scripts/test_union_query_mybatis_plus.sh- 测试脚本doc/plans/2026-02-05-intermediary-blacklist-union-query-mybatis-plus.md- 本文档
删除的文件
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
测试用例:
- Test 1: UNION ALL查询全部中介
- Test 2: 按类型筛选个人中介
- Test 3: 按类型筛选实体中介
- Test 4: 按姓名模糊查询
- Test 5: 按证件号精确查询
- Test 6: MyBatis Plus分页功能测试
- Test 7: 组合查询测试
- 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查询 | 手动 | 自动 |
| 分页计算 | 手动 | 自动 |
九、最佳实践
基于本次重构,总结以下最佳实践:
-
遵循框架规范
- 优先使用框架提供的标准实现方式
- 参考其他模块的成熟实现
-
分页查询模式
// 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> -
联合查询优化
- 使用UNION ALL而不是多个分支
- 在最外层使用WHERE进行过滤
- 避免在XML中写LIMIT和OFFSET
-
参数传递
- Page对象作为第一个参数
- 查询条件使用@Param包装
- 避免在实体中混入分页参数
十、后续建议
-
性能监控
- 监控UNION ALL查询的执行计划
- 优化索引以提升查询性能
-
功能扩展
- 考虑添加更多排序字段选项
- 考虑支持批量导出的流式查询
-
代码优化
- 其他模块如有类似实现,建议统一改造
- 建立统一的分页查询模板
实现日期: 2026-02-05 实现人: Claude Code 版本: v2.1 (MyBatis Plus分页插件版本) 参考模块: CcdiEmployeeController/CcdiEmployeeServiceImpl