Files
ccdi/doc/plans/2026-02-05-intermediary-blacklist-union-query-mybatis-plus.md

369 lines
11 KiB
Markdown
Raw Normal View History

2026-02-05 13:33:27 +08:00
# 中介黑名单联合查询功能重构实现总结 (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` 的实现方式:
```java
// 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-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiIntermediaryMapper.java`
**修改前:**
```java
List<CcdiIntermediaryVO> selectIntermediaryList(CcdiIntermediaryQueryDTO queryDTO);
long selectIntermediaryCount(CcdiIntermediaryQueryDTO queryDTO);
```
**修改后:**
```java
Page<CcdiIntermediaryVO> selectIntermediaryList(
Page<CcdiIntermediaryVO> page,
@Param("query") CcdiIntermediaryQueryDTO queryDTO
);
```
**关键点:**
- 第一个参数是 `Page` 对象
- 查询条件使用 `@Param` 注解包装
- 返回类型是 `Page<Vo>`
- 删除了单独的count查询方法
#### 2. XML Mapper文件
**文件:** `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiIntermediaryMapper.xml`
**修改前v2.0**
```xml
<!-- 三个独立的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**
```xml
<!-- 统一的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-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java`
**修改前v2.0**
```java
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**
```java
public Page<CcdiIntermediaryVO> selectIntermediaryPage(Page<CcdiIntermediaryVO> page, CcdiIntermediaryQueryDTO queryDTO) {
// 直接调用Mapper的联合查询方法MyBatis Plus会自动处理分页
return intermediaryMapper.selectIntermediaryList(page, queryDTO);
}
```
**关键点:**
- 一行代码搞定
- MyBatis Plus自动处理count查询、分页SQL注入、结果封装
- 无需手动计算分页参数
#### 4. QueryDTO清理
**文件:** `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiIntermediaryQueryDTO.java`
**删除字段:**
```java
// 不再需要分页信息通过Page对象传递
private Integer pageNum;
private Integer pageSize;
```
## 四、技术实现细节
### 4.1 MyBatis Plus分页插件工作原理
1. **拦截器机制**
- MyBatis Plus使用拦截器在SQL执行前拦截
- 自动在SQL后面添加LIMIT和OFFSET
- 自动执行COUNT查询获取total
2. **分页SQL生成**
```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: `PageDomain``Page<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:**
```java
// QueryDTO包含分页参数
queryDTO.setPageNum(0);
queryDTO.setPageSize(10);
mapper.selectList(queryDTO);
// XML中直接使用
#{pageNum}, #{pageSize}
```
**v2.1:**
```java
// Page对象单独传递
Page<CcdiIntermediaryVO> page = new Page<>(1, 10);
mapper.selectList(page, queryDTO);
// XML中通过@Param包装
#{query.intermediaryType}, #{query.name}
```
## 五、文件清单
### 修改的文件
1. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiBizIntermediary.java` - 删除冗余字段,修复字段映射
2. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiIntermediaryQueryDTO.java` - 删除分页参数
3. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiIntermediaryMapper.java` - 修改方法签名
4. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java` - 简化分页逻辑
5. `ruoyi-ccdi/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 执行测试
```bash
# 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. **分页查询模式**
```java
// 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