在 Spring Boot 项目中,处理列表查询时往往难以避免“复杂搜索”这一核心挑战。
典型场景包括:
用户管理:需支持按姓名、手机号、状态、注册时间等多个字段组合筛选。
商品搜索:涉及分类、多选标签、价格区间、关键词匹配及排序等复杂条件。
实际上,复杂搜索的实现本身并不困难,真正的难点在于如何使搜索逻辑保持清晰、可维护且易于扩展。本文将提供一套可在日常项目中直接复用的动态查询模板,其核心流程为:
SearchDTO → Service 层动态 Wrapper → Mapper → SearchVO
一、复杂搜索代码混乱的根源
通常存在以下几类典型问题:
1. DTO 字段设计混乱,所有条件堆积于同一对象
示例:
```java
String name;
String phone;
Integer status;
String keyword;
List<Long> categoryIds;
BigDecimal minPrice;
BigDecimal maxPrice;
String startTime;
String endTime;
```
随着字段不断增加,Service 层中的条件判断也随之膨胀,最终导致代码难以维护。
2. Wrapper 中充斥大量条件判断语句
例如:
```java
if (dto.getName() != null)
wrapper.like(User::getName, dto.getName());
if (dto.getStatus() != null)
wrapper.eq(User::getStatus, dto.getStatus());
if (dto.getStartTime() != null)
wrapper.ge(User::getCreateTime, dto.getStartTime());
// ...
```
数十行条件判断并列呈现,可读性与可维护性均较差。
3. 相同查询逻辑在多处重复实现
相似的“模糊搜索 + 多选 + 区间筛选”逻辑,可能在多个接口中重复编写,缺乏抽象与复用。
4. 条件逻辑难以扩展
若新增一个“标签筛选”条件,需从 Controller 至 SQL 逐层修改,变更成本高。
最终导致的结果是:搜索接口往往成为项目中维护难度最高的部分之一。
二、复杂搜索的核心难点
其本质在于查询条件的动态性与组合不确定性:
1.部分字段为可选条件,传则查询,不传则忽略;
2.部分字段需支持区间查询;
3.部分字段为多选值(IN 查询);
4.部分字段需进行模糊匹配;
5.部分条件需动态组合 AND/OR 逻辑;
6.部分查询涉及关联表筛选。
这些组合可能随时变化,因此代码必须保持结构清晰、易于调整。
解决思路应为:
将所有搜索逻辑通过统一方式进行封装,并嵌入 Wrapper 中,使其具备可配置、可扩展、可复用的特性。
三、SearchDTO:构建结构化的查询入参
设计良好的搜索接口,应从规范的数据传输对象开始。
示例:
```java
@Data
public class UserSearchDTO extends PageRequest {
private String keyword; // 统一关键字匹配(姓名/手机号)
private Integer status; // 精确匹配
private List<Long> roleIds; // 多选条件(IN 查询)
private LocalDateTime beginTime; // 时间区间起点
private LocalDateTime endTime; // 时间区间终点
}
```
设计要点:
1. 关键字统一为 keyword 字段:避免为每个可模糊查询的字段单独定义,提升接口简洁性。
2. 时间区间字段成对出现:采用 `beginTime` 与 `endTime` 的规范命名,便于统一处理。
3. 多选条件使用集合类型:便于直接应用于 `IN` 查询。
4. DTO 仅承担查询条件载体职责:不参与具体业务逻辑。
四、Service 层实现:动态 Wrapper 模板
Service 层应负责组合查询条件,并保持代码清晰。
示例模板:
```java
public PageVO<UserVO> page(UserSearchDTO dto) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 1. 关键字多字段搜索
if (StringUtils.isNotBlank(dto.getKeyword())) {
wrapper.and(w >
w.like(User::getName, dto.getKeyword())
.or()
.like(User::getPhone, dto.getKeyword())
);
}
// 2. 状态精确匹配
wrapper.eq(dto.getStatus() != null, User::getStatus, dto.getStatus());
// 3. 多选角色查询
wrapper.in(CollectionUtils.isNotEmpty(dto.getRoleIds()),
User::getRoleId, dto.getRoleIds());
// 4. 时间区间查询
wrapper.ge(dto.getBeginTime() != null, User::getCreateTime, dto.getBeginTime());
wrapper.le(dto.getEndTime() != null, User::getCreateTime, dto.getEndTime());
// 5. 执行分页查询
Page<User> page = userMapper.selectPage(dto.toPage(), wrapper);
return PageVO.of(page, UserVO.class);
}
```
此模板结构清晰,覆盖大部分常见查询场景,并具有良好的扩展性。
五、通用查询工具类封装
为提升代码复用性,可将常用查询条件封装为工具类:
1. 模糊查询封装
```java
public static <T> void like(LambdaQueryWrapper<T> wrapper,
boolean condition,
SFunction<T, ?> column,
String value) {
if (condition && StringUtils.isNotBlank(value)) {
wrapper.like(column, value);
}
}
```
调用方式:
```java
WrapperHelper.like(wrapper, true, User::getName, dto.getName());
```
2. 多字段关键字查询封装
```java
public static <T> void keyword(LambdaQueryWrapper<T> wrapper,
String keyword,
SFunction<T, ?>... columns) {
if (StringUtils.isBlank(keyword)) return;
wrapper.and(w > {
for (int i = 0; i < columns.length; i++) {
if (i == 0) {
w.like(columns[i], keyword);
} else {
w.or().like(columns[i], keyword);
}
}
});
}
调用方式:
```java
WrapperHelper.keyword(wrapper, dto.getKeyword(),
User::getName,
User::getPhone
);
```
3. 区间查询封装
```java
public static <T, R extends Comparable<R>>
void range(LambdaQueryWrapper<T> wrapper,
R begin, R end,
SFunction<T, R> column) {
wrapper.ge(begin != null, column, begin);
wrapper.le(end != null, column, end);
}
```
调用方式:
```java
WrapperHelper.range(wrapper, dto.getBeginTime(), dto.getEndTime(), User::getCreateTime);
```
4. 集合条件查询封装
```java
public static <T> void in(LambdaQueryWrapper<T> wrapper,
List<?> list,
SFunction<T, ?> column) {
wrapper.in(CollectionUtils.isNotEmpty(list), column, list);
}
```
最终效果
Service 层代码将变得极为简洁:
```java
WrapperHelper.keyword(wrapper, dto.getKeyword(), User::getName, User::getPhone);
WrapperHelper.eq(wrapper, dto.getStatus(), User::getStatus);
WrapperHelper.in(wrapper, dto.getRoleIds(), User::getRoleId);
WrapperHelper.range(wrapper, dto.getBeginTime(), dto.getEndTime(), User::getCreateTime);
```
六、典型业务场景示例
场景一:商品多条件搜索(含排序)
SearchDTO:
```java
String keyword;
List<Long> categoryIds;
List<Integer> statusList;
BigDecimal minPrice;
BigDecimal maxPrice;
String sortField;
String sortOrder;
```
Service 实现:
```java
WrapperHelper.keyword(wrapper, dto.getKeyword(), Goods::getName, Goods::getDesc);
WrapperHelper.in(wrapper, dto.getCategoryIds(), Goods::getCategoryId);
WrapperHelper.in(wrapper, dto.getStatusList(), Goods::getStatus);
WrapperHelper.range(wrapper, dto.getMinPrice(), dto.getMaxPrice(), Goods::getPrice);
SortHelper.applySort(wrapper, dto.getSortField(), dto.getSortOrder());
```
场景二:用户复合条件筛选(OR 逻辑)
```java
wrapper.and(w > {
w.eq(dto.getGender() != null, User::getGender, dto.getGender());
w.or(dto.getIsVip() != null).eq(User::getIsVip, dto.getIsVip());
});
```
场景三:多表关联筛选(如标签过滤)
```java
wrapper.inSql(User::getId,
"SELECT user_id FROM user_tag WHERE tag_id IN "
+ convertToSql(dto.getTagIds())
);
```
七、动态查询模板的收益
采用统一模板后,代码结构将呈现以下优势:
1.Controller 层保持简洁:仅负责参数传递与结果返回。
2.Service 层逻辑统一、可读性强、易于维护:通过工具类方法组合条件,避免重复代码。
3.DTO 设计规范清晰:采用 `keyword` + `List` + `range` 的标准化结构。
查询逻辑高度复用、可灵活组合、易于扩展:新增条件或调整逻辑时,只需在工具类或 DTO 中集中修改。
自此,您将彻底告别:
满屏的 if/else 条件判断;
搜索逻辑在不同接口中的重复实现;
因单个字段变更引发的连锁式修改。
通过统一的动态查询模板,任何复杂搜索场景均可从容应对。
总结
复杂搜索的难点并非技术实现,而在于:
1.查询条件不断累积导致字段膨胀;
2.业务逻辑分散且重复,难以维护;
3.每次开发均从零开始,效率低下;
4.代码逐渐演变为难以管理的条件判断堆砌。
通过本文介绍的动态查询模板,您可将搜索逻辑标准化、模块化,从而显著提升代码质量与开发效率。
来源:小程序app开发|ui设计|软件外包|IT技术服务公司-木风未来科技-成都木风未来科技有限公司