@ApiOperation("分页列表") @RequestMapping(value = "/list public IPage<TimeCardRes> pageWithJoin(IPage<TimeCardRes> page, TimeCardRes conditionEntity, Map<String, String[]> parameters) { param = handleRequestParameters(parameters); MPJLambdaWrapper<TimeCard> mpjLW = new MPJLambdaWrapper<>(TimeCard.class) .selectAll(TimeCard.class) .selectAs(ActivityInfo::getName, TimeCardRes::getActivityName) .selectAs(ActivityInfo::getCode, TimeCardRes::getActivityCode) .selectAs(SysUser::getRealname, TimeCardRes::getRealname) .leftJoin(ActivityInfo.class, ActivityInfo::getId, TimeCard::getActivityId) .leftJoin(SysUser.class, SysUser::getId, TimeCard::getUserId) .like(ActivityInfo::getName, conditionEntity.getActivityName()) .like(SysUser::getRealname, conditionEntity.getRealname()) .le(TimeCard::getChargeDate, param.getChargeDate_start()) .ge(TimeCard::getChargeDate, param.getChargeDate_end()) .orderByDesc(TimeCard::getChargeDate); // Must be called after selects and joins are set // MPJQueryGenerator.installMPJ(mpjLW, conditionEntity, parameters); return this.selectJoinListPage(page, TimeCardRes.class, mpjLW); } // 前端请求参数处理 public 返回参数处理结果 handleRequestParameters(Map<String, String[]> parameters) { final String SQL_RULES_COLUMN = "SQL_RULES_COLUMN"; final String BEGIN = "_begin"; final String END = "_end"; final String STAR = "*"; final String COMMA = ","; final String NOT_EQUAL = "!"; final String NOT_LIKE = "_notLike"; final String QUERY_SEPARATE_KEYWORD = " "; final String SUPER_QUERY_PARAMS = "superQueryParams"; final String ORDER_COLUMN = "column"; final String ORDER_TYPE = "order"; final String ORDER_TYPE_ASC = "ASC"; // 处理参数 } }
时间: 2025-07-08 21:20:13 浏览: 4
<think>好的,我现在需要解决用户关于MyBatis Plus分页查询联合多表关联的问题,特别是使用Lambda Wrapper的示例以及前端参数处理的逻辑。首先,我需要回忆一下MyBatis Plus的相关知识,尤其是分页和多表查询的部分。
用户提到了他们之前通过重写MyBatis Plus的page方法来实现多表分页查询,但现在希望不破坏原有的page方法,而是自定义一个分页查询。根据引用[2],用户提供的UserMapper接口中有一个selectUserPage方法,接受Page和Wrapper参数。这说明用户可能已经知道如何定义自定义的分页方法,但需要具体的示例,特别是结合多表关联和Lambda Wrapper的情况。
首先,MyBatis Plus的分页功能需要配置分页插件,这一步应该是基础,但用户可能已经做了,不过为了完整性,还是应该在回答中提到。然后,用户需要的是多表查询的分页,这通常涉及到自定义SQL,因为MyBatis Plus的BaseMapper默认的方法可能无法处理复杂的联表查询。
接下来,用户提到了使用Lambda Wrapper。Lambda Wrapper在MyBatis Plus中用于构建类型安全的查询条件,避免硬编码字段名。但是在多表关联的情况下,直接使用Lambda Wrapper可能会有问题,因为每个Wrapper通常关联到一个实体类。这时候可能需要手动构建查询条件,或者在Wrapper中处理关联表的条件。例如,在联表查询时,可能需要使用JOIN语句,这时候在XML文件中编写SQL,并通过Wrapper传递参数。
然后,关于前端参数处理,用户需要知道如何将前端的参数(比如页码、每页大小、过滤条件等)正确传递到后端,并转换为Page对象和Wrapper的条件。例如,前端可能会传pageNum和pageSize,后端需要将这些参数封装到Page对象中。同时,过滤条件可能需要动态构建LambdaWrapper,比如根据用户输入的姓名进行模糊查询,或者根据其他字段进行排序。
在示例部分,我需要提供一个Mapper接口的方法,例如selectPageWithJoin,该方法接受Page和Wrapper作为参数。然后在XML文件中编写对应的SQL,使用JOIN语句关联多表,并动态添加Wrapper中的条件。同时,在Service层中调用这个方法,传递分页参数和查询条件。
另外,用户可能对Lambda Wrapper在多表查询中的使用有疑问。例如,如何关联多个表的字段。这时候可能需要手动指定字段名,或者在Wrapper中使用字符串,但为了类型安全,可能需要在实体类中定义别名,或者在DTO中处理。例如,使用LambdaWrapper时,如果涉及其他表的字段,可能需要使用字符串表示,或者通过自定义SQL片段来处理。
关于前端参数解析,需要说明如何接收参数,比如使用@RequestParam获取pageNum和pageSize,构造Page对象。对于过滤条件,可以使用对象接收,然后动态构建Wrapper的条件,比如like、eq等方法。例如,如果前端传了name参数,后端可以使用wrapper.like(User::getName, name)来添加条件。
可能还需要考虑排序参数的处理,前端可能会传sortField和sortOrder,后端需要将这些参数添加到Page对象的OrderBy中,或者使用Wrapper的orderBy方法。
总结一下,回答的结构应该是:
1. 配置分页插件(确认基础配置)
2. 创建Mapper接口的自定义方法,使用Page和Wrapper参数
3. 在XML中编写联表查询的SQL,动态应用Wrapper的条件
4. Service层调用Mapper方法,处理分页逻辑
5. 前端参数如何解析为Page和Wrapper,包括分页参数和过滤条件
6. 提供Lambda Wrapper的示例,如何处理多表字段的条件
7. 可能的优化或注意事项,比如SQL性能、参数校验等
现在需要检查是否有遗漏的部分,比如是否需要处理多表查询时的结果映射?可能需要使用resultMap或者在DTO中定义字段。另外,分页查询的总数统计是否正确,尤其是在联表查询时,是否需要使用count语句优化。
例如,在XML中的SQL需要确保分页的正确性,以及count查询的效率。有时候联表查询会影响count的结果,可能需要单独的count语句或者子查询优化。
此外,Lambda Wrapper在多表查询中的限制,比如无法直接引用其他表的字段,这时候可能需要手动添加条件,或者在Wrapper中使用字符串字段名,但这样会失去类型安全的好处。因此,可能需要权衡,或者使用其他方法如JOIN查询中在ON子句里处理条件。
最后,确保提供的代码示例正确,特别是XML中的动态条件如何使用Wrapper传递的参数。例如,在XML中使用${ew.customSqlSegment}来插入Wrapper生成的SQL条件,并且ew需要是Wrapper的别名。
在示例代码中,还需要注意分页参数和Wrapper的构建,比如在Service层如何创建Page对象,添加排序,以及构建LambdaWrapper的条件。前端参数的接收可能需要一个DTO来封装分页和过滤条件,或者通过多个参数接收。
可能还需要提到如何处理前端传递的排序字段,例如通过Page的addOrder方法,或者使用Wrapper的orderBy方法。同时,参数校验也是重要的,比如pageNum和pageSize的合法性检查,避免负数或过大的每页大小。
总之,回答需要覆盖配置、Mapper接口、XML SQL、Service层逻辑、前端参数处理,以及Lambda Wrapper在多表查询中的具体应用,同时提供可运行的示例代码,并解释关键步骤和注意事项。</think>### MyBatis Plus多表分页查询实现步骤
#### 1. 配置分页插件
确保项目中已配置MyBatis Plus分页插件:
```java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
```
#### 2. 定义Mapper接口
创建自定义分页方法,支持多表关联查询:
```java
public interface UserMapper extends BaseMapper<User> {
IPage<UserDto> selectPageWithJoin(
@Param("page") Page<UserDto> page,
@Param(Constants.WRAPPER) Wrapper<User> wrapper
);
}
```
#### 3. XML映射文件
编写联表查询SQL(示例以`user`表关联`dept`表):
```xml
<select id="selectPageWithJoin" resultType="com.example.dto.UserDto">
SELECT
u.id, u.name, u.age, d.dept_name
FROM
user u
LEFT JOIN
dept d ON u.dept_id = d.id
${ew.customSqlSegment} <!-- 自动注入Wrapper条件 -->
</select>
```
#### 4. Service层实现
构建分页参数和查询条件:
```java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
public Page<UserDto> getUsers(int pageNum, int pageSize, String nameFilter) {
Page<UserDto> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(nameFilter), User::getName, nameFilter);
return userMapper.selectPageWithJoin(page, wrapper);
}
}
```
#### 5. 前端参数处理
前端请求参数示例(JSON):
```json
{
"pageNum": 1,
"pageSize": 10,
"nameFilter": "张",
"sortField": "age",
"sortOrder": "desc"
}
```
后端Controller解析逻辑:
```java
@PostMapping("/page")
public Result<?> getPage(@RequestBody PageRequest params) {
// 构建分页对象
Page<UserDto> page = new Page<>(params.getPageNum(), params.getPageSize());
// 添加排序条件
if (StringUtils.isNotBlank(params.getSortField())) {
page.addOrder(new OrderItem(params.getSortField(), "asc".equals(params.getSortOrder())));
}
// 构建查询条件
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
.like(StringUtils.isNotBlank(params.getNameFilter()), User::getName, params.getNameFilter());
return Result.success(userService.getUsers(page, wrapper));
}
```
### 关键实现细节
1. **动态字段映射**
- 创建`UserDto`包含`deptName`字段,使用`@TableField(select = false)`避免自动映射
- 在XML中明确指定别名`d.dept_name as deptName`
2. **Lambda条件安全**
```java
wrapper.like(User::getName, "张") // 类型安全的字段引用
.eq(User::getDeptId, 2)
```
3. **分页优化建议**
- 添加`PageOptimizeInterceptor`优化COUNT语句
- 复杂查询建议使用`@Select`注解自定义COUNT查询
### 注意事项
1. **N+1查询问题**
- 避免在循环中查询关联表数据
- 优先使用JOIN查询代替多次单表查询
2. **参数校验**
```java
if (pageNum < 1 || pageSize > 100) {
throw new IllegalArgumentException("分页参数异常");
}
```
3. **SQL注入防护**
- 禁止直接拼接`${param}`接收前端排序字段
- 使用字段白名单校验:
```java
private static final Set<String> ALLOW_SORT_FIELDS = Set.of("id", "create_time");
if (!ALLOW_SORT_FIELDS.contains(sortField)) {
throw new InvalidParameterException("非法排序字段");
}
```
### 典型应用场景
1. 管理后台用户列表(带部门名称显示)
2. 订单分页查询(关联商品信息)
3. 数据分析报表(多维度联合查询)
### 扩展优化方案
1. **自动参数映射**
使用`@ObjectParser`注解实现前端JSON到Wrapper的自动转换:
```java
@PostMapping("/search")
public void search(@ObjectParser(parser = WrapperParser.class) QueryWrapper<User> wrapper) {
// 直接接收前端生成的Wrapper
}
```
2. **通用分页封装**
创建`PageParam`基类:
```java
@Data
public class PageParam {
private Integer pageNum = 1;
private Integer pageSize = 10;
private String sortField;
private String sortOrder;
}
```
3. **多租户过滤**
在Wrapper中自动添加租户条件:
```java
wrapper.eq(User::getTenantId, SecurityUtils.getCurrentTenantId());
```
### 常见问题解决
**Q1: 如何保证分页总数准确?**
- 复杂查询时配置`optimizeCountSql=true`
- 手动指定COUNT查询语句:
```xml
<select id="selectPageWithJoin_count" resultType="java.lang.Long">
SELECT COUNT(1) FROM user u ${ew.customSqlSegment}
</select>
```
**Q2: 多表字段同名冲突?**
- 使用`@TableField(value = "u.name")`显式指定别名
- 在DTO中定义:
```java
@TableField("d.dept_name")
private String deptName;
```
**Q3: 如何实现嵌套查询?**
```xml
<select id="selectNested" resultMap="userResultMap">
SELECT * FROM user WHERE id IN
<foreach collection="wrapper.entity.ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
```
### 性能优化建议
1. **索引优化**
- 为`dept_id`等关联字段添加索引
- 联合查询涉及的排序字段建立复合索引
2. **缓存策略**
- 使用`@CacheNamespace`注解缓存部门表数据
- 对分页结果实施二级缓存:
```xml
<cache eviction="LRU" flushInterval="300000"/>
```
3. **异步加载**
对非核心字段使用异步查询:
```java
CompletableFuture.supplyAsync(() -> userMapper.selectDetail(userId));
```
### 示例完整调用链路
```
前端请求
→ Controller接收参数并校验
→ Service构建Page和Wrapper
→ Mapper执行XML中定义的SQL
→ 自动注入Wrapper条件
→ 返回分页结果
→ 结果转换层过滤敏感字段
→ 返回标准化响应结构
```
### 最终实现效果
- 支持多表联合分页查询
- 保持LambdaWrapper的类型安全特性
- 实现动态条件过滤
- 完整的分页参数处理
- 可扩展的排序支持
- 防御性参数校验机制
通过这种实现方式,可以在不破坏MyBatis Plus原生分页功能的前提下,灵活处理复杂的多表关联查询需求,同时保证代码的可维护性和类型安全性[^2]。
阅读全文
相关推荐

















