369 lines
11 KiB
Markdown
369 lines
11 KiB
Markdown
|
|
# 中介黑名单联合查询功能重构实现总结 (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
|