Files
ccdi/assets/requirements/plans/2026-02-05-intermediary-blacklist-union-query-mybatis-plus.md
2026-03-03 16:14:16 +08:00

402 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 中介黑名单联合查询功能重构实现总结 (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-info-collection/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-info-collection/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-info-collection/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-info-collection/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-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 执行测试
```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