MyBatis-Plus(MP)的條件構造器是其核心亮點之一,用於動態拼接 SQL WHERE 條件,替代手寫複雜的 SQL 字符串,支持類型安全、鏈式調用,大幅提升查詢開發效率。本文從「核心 API、分類使用、高級技巧、避坑指南」四個維度,全面講解條件構造器的使用。
一、核心概念與分類
1. 條件構造器體系
plaintext
AbstractWrapper (抽象父類)
├─ QueryWrapper (查詢/刪除條件構造器)
├─ UpdateWrapper (更新條件構造器)
└─ LambdaQueryWrapper/LambdaUpdateWrapper (Lambda版,類型安全)
|
構造器類型
|
特點
|
適用場景
|
|
QueryWrapper
|
基於字段名字符串拼接條件,靈活但無類型校驗
|
快速原型、簡單條件
|
|
LambdaQueryWrapper
|
基於 Lambda 表達式(類::方法),編譯期校驗字段名,避免拼寫錯誤
|
生產環境、複雜條件、類型安全
|
|
UpdateWrapper
|
專用於 UPDATE 語句,支持 SET 字段 + WHERE 條件
|
更新操作
|
|
LambdaUpdateWrapper
|
Lambda 版 UpdateWrapper,類型安全
|
生產環境更新操作
|
2. 核心依賴
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
二、基礎用法(以 WebGIS 點位表gis_point為例)
1. QueryWrapper 基礎示例
java運行
// 實體類:GisPoint(id, name, lng, lat, type, create_time, is_deleted)
QueryWrapper<GisPoint> queryWrapper = new QueryWrapper<>();
// 1. 等值查詢(eq =)
queryWrapper.eq("type", "POI"); // WHERE type = 'POI'
// 2. 不等值查詢(ne !=)
queryWrapper.ne("is_deleted", 1); // AND is_deleted != 1
// 3. 範圍查詢(gt >, ge >=, lt <, le <=)
queryWrapper.gt("lng", 104.0) // AND lng > 104.0
.le("lat", 30.7); // AND lat <= 30.7
// 4. 模糊查詢(like 包含, likeLeft 左模糊, likeRight 右模糊)
queryWrapper.like("name", "春熙路"); // AND name LIKE '%春熙路%'
queryWrapper.likeLeft("name", "成都"); // AND name LIKE '%成都'
// 5. 空值判斷(isNull, isNotNull)
queryWrapper.isNotNull("lng"); // AND lng IS NOT NULL
// 6. 排序(orderByAsc, orderByDesc)
queryWrapper.orderByDesc("create_time"); // ORDER BY create_time DESC
// 7. 執行查詢
List<GisPoint> list = gisPointMapper.selectList(queryWrapper);
2. LambdaQueryWrapper(推薦)
java運行
LambdaQueryWrapper<GisPoint> lambdaWrapper = new LambdaQueryWrapper<>();
// 1. 等值查詢(Lambda表達式指定字段)
lambdaWrapper.eq(GisPoint::getType, "POI")
.ne(GisPoint::getIsDeleted, 1);
// 2. 範圍 + 模糊查詢
lambdaWrapper.gt(GisPoint::getLng, 104.0)
.between(GisPoint::getLat, 30.6, 30.7) // lat BETWEEN 30.6 AND 30.7
.like(GisPoint::getName, "春熙路");
// 3. IN查詢
lambdaWrapper.in(GisPoint::getType, "POI", "監控點", "公交站"); // type IN ('POI','監控點','公交站')
// 4. NOT IN查詢
lambdaWrapper.notIn(GisPoint::getId, 1, 2, 3); // id NOT IN (1,2,3)
// 5. 分組 + 聚合
lambdaWrapper.select(GisPoint::getType, "COUNT(*) as count")
.groupBy(GisPoint::getType) // GROUP BY type
.having("count > 10"); // HAVING count > 10
// 6. 執行查詢
List<GisPoint> list = gisPointService.list(lambdaWrapper);
// 分頁查詢
IPage<GisPoint> page = gisPointService.page(new Page<>(1, 10), lambdaWrapper);
3. UpdateWrapper 示例
java運行
UpdateWrapper<GisPoint> updateWrapper = new UpdateWrapper<>();
// SET 字段 + WHERE 條件
updateWrapper.set("name", "成都春熙路核心區") // SET name = '成都春熙路核心區'
.set("update_time", LocalDateTime.now()) // SET update_time = ?
.eq("id", 1) // WHERE id = 1
.eq("type", "POI"); // AND type = 'POI'
// 執行更新
boolean success = gisPointMapper.update(null, updateWrapper);
4. LambdaUpdateWrapper 示例
java運行
LambdaUpdateWrapper<GisPoint> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.set(GisPoint::getName, "天府廣場新名稱")
.set(GisPoint::getUpdateTime, LocalDateTime.now())
.eq(GisPoint::getId, 2)
.gt(GisPoint::getCreateTime, LocalDateTime.of(2024, 1, 1, 0, 0));
boolean success = gisPointService.update(lambdaUpdateWrapper);
三、高級功能實戰
1. 複雜條件(AND/OR 嵌套)
場景:查詢「類型為 POI 且 名稱含春熙路」OR「類型為監控點 且 經度 > 104.1」
java運行
LambdaQueryWrapper<GisPoint> wrapper = new LambdaQueryWrapper<>();
// 方式1:嵌套or()
wrapper.and(w -> w.eq(GisPoint::getType, "POI").like(GisPoint::getName, "春熙路"))
.or(w -> w.eq(GisPoint::getType, "監控點").gt(GisPoint::getLng, 104.1));
// 方式2:手動控制AND/OR(等價)
wrapper.eq(GisPoint::getType, "POI")
.like(GisPoint::getName, "春熙路")
.or()
.eq(GisPoint::getType, "監控點")
.gt(GisPoint::getLng, 104.1);
// 複雜嵌套(AND包裹OR)
wrapper.eq(GisPoint::getIsDeleted, 0)
.and(w -> w.like(GisPoint::getName, "成都").or().like(GisPoint::getName, "天府"));
2. 自定義 SQL 片段(apply)
java運行
// 示例1:MySQL空間函數 - 查詢距離指定座標1公里內的點位
LambdaQueryWrapper<GisPoint> wrapper = new LambdaQueryWrapper<>();
// ST_Distance_Sphere:計算兩點間球面距離(米)
wrapper.apply("ST_Distance_Sphere(POINT(lng, lat), POINT({0}, {1})) < {2}", 104.06, 30.66, 1000);
// 示例2:PostGIS空間函數 - 判斷點位是否在面內
wrapper.apply("ST_Contains(ST_GeomFromText('{0}'), geom)", "POLYGON((104.0 30.6, 104.1 30.6, 104.1 30.7, 104.0 30.7, 104.0 30.6))");
// 示例3:動態SQL(防止SQL注入,使用{0}佔位符)
wrapper.apply("DATE_FORMAT(create_time, '%Y-%m') = {0}", "2024-10");
3. 字段篩選(select)
java運行
// 方式1:指定單個/多個字段(Lambda)
LambdaQueryWrapper<GisPoint> wrapper = new LambdaQueryWrapper<>();
wrapper.select(GisPoint::getId, GisPoint::getName, GisPoint::getLng, GisPoint::getLat)
.eq(GisPoint::getType, "POI");
// 方式2:排除指定字段(僅QueryWrapper支持)
QueryWrapper<GisPoint> queryWrapper = new QueryWrapper<>();
queryWrapper.select(GisPoint.class, info -> !info.getColumn().equals("create_time") && !info.getColumn().equals("update_time"))
.eq("type", "POI");
4. 條件拼接(動態 SQL)
java運行
// 前端傳入的查詢參數
String name = "春熙路"; // 可能為null
String type = "POI"; // 可能為null
Double minLng = 104.0; // 可能為null
LambdaQueryWrapper<GisPoint> wrapper = new LambdaQueryWrapper<>();
// 1. 基礎動態條件(非空才拼接)
if (StringUtils.isNotBlank(name)) {
wrapper.like(GisPoint::getName, name);
}
if (StringUtils.isNotBlank(type)) {
wrapper.eq(GisPoint::getType, type);
}
if (minLng != null) {
wrapper.gt(GisPoint::getLng, minLng);
}
// 2. 鏈式動態條件(MP3.0+推薦)
wrapper.like(StringUtils.isNotBlank(name), GisPoint::getName, name)
.eq(StringUtils.isNotBlank(type), GisPoint::getType, type)
.gt(minLng != null, GisPoint::getLng, minLng);
5. 子查詢(inSql/exists)
java運行
// 示例1:IN子查詢
LambdaQueryWrapper<GisPoint> wrapper = new LambdaQueryWrapper<>();
wrapper.inSql(GisPoint::getId, "SELECT id FROM gis_point WHERE type = 'POI' LIMIT 10");
// 示例2:EXISTS子查詢
wrapper.exists("SELECT 1 FROM gis_area WHERE gis_area.id = gis_point.area_id AND gis_area.name = '錦江區'");
// 示例3:NOT IN子查詢
wrapper.notInSql(GisPoint::getId, "SELECT id FROM gis_point WHERE create_time < '2024-01-01'");
6. 邏輯刪除兼容
java運行
LambdaQueryWrapper<GisPoint> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(GisPoint::getId, 1)
.ignoreLogicDelete(); // 忽略邏輯刪除,查詢已刪除數據
四、避坑指南與最佳實踐
1. 常見坑點
|
問題場景
|
原因
|
解決方案
|
|
字段名拼寫錯誤
|
QueryWrapper 手寫字符串字段名,大小寫 / 拼寫錯誤
|
改用 LambdaQueryWrapper,編譯期校驗
|
|
OR 條件導致邏輯錯誤
|
未嵌套 AND/OR,條件範圍超出預期
|
使用 |
|
SQL 注入風險
|
apply 方法直接拼接字符串(如 |
使用 |
|
空值條件生效
|
傳入 null 到 eq 方法(如 |
先判斷參數非空,或使用 |
|
分頁查詢總條數為 0
|
條件構造器拼接了錯誤的 WHERE 條件,或邏輯刪除過濾了所有數據
|
打印 SQL 日誌(配置 log-impl),檢查最終生成的 SQL
|
2. 最佳實踐
(1)優先使用 LambdaQueryWrapper
- 類型安全,避免字段名拼寫錯誤;
- 重構實體類字段時,IDE 可自動同步條件構造器中的字段;
- 代碼可讀性更高(直接看到字段名,而非字符串)。
(2)封裝通用查詢條件
java運行
/**
* 通用GIS查詢條件構造器
*/
public class GisQueryWrapper<T> extends LambdaQueryWrapper<T> {
public GisQueryWrapper() {
// 默認拼接邏輯刪除條件
this.eq(BaseGisEntity::getIsDeleted, 0);
}
/**
* 追加數據權限條件(如僅查詢當前用户所屬區域)
*/
public GisQueryWrapper<T> dataScope(Long userId) {
// 示例:根據用户ID查詢所屬區域
this.inSql(BaseGisEntity::getAreaId, "SELECT area_id FROM sys_user_area WHERE user_id = " + userId);
return this;
}
}
// 使用
GisQueryWrapper<GisPoint> wrapper = new GisQueryWrapper<>();
wrapper.like(GisPoint::getName, "成都")
.dataScope(1L); // 追加數據權限
(3)打印 SQL 日誌,調試條件
yaml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL到控制枱
(4)批量條件拼接用鏈式調用
java運行
// 反例:多層if-else
if (name != null) {
wrapper.like("name", name);
}
if (type != null) {
wrapper.eq("type", type);
}
if (minLng != null) {
wrapper.gt("lng", minLng);
}
// 正例:鏈式調用
wrapper.like(Objects.nonNull(name), GisPoint::getName, name)
.eq(Objects.nonNull(type), GisPoint::getType, type)
.gt(Objects.nonNull(minLng), GisPoint::getLng, minLng);
(5)複雜 SQL 優先用 XML / 註解
五、WebGIS 場景專屬示例
1. 空間範圍查詢(矩形 / 圓形)
java運行
// 示例1:矩形範圍查詢(西南角-東北角)
LambdaQueryWrapper<GisPoint> wrapper = new LambdaQueryWrapper<>();
wrapper.between(GisPoint::getLng, 104.0, 104.2) // 經度範圍
.between(GisPoint::getLat, 30.5, 30.8); // 緯度範圍
// 示例2:圓形範圍查詢(MySQL)
Double centerLng = 104.06;
Double centerLat = 30.66;
Double radius = 2000; // 2公里
wrapper.apply("ST_Distance_Sphere(POINT(lng, lat), POINT({0}, {1})) <= {2}", centerLng, centerLat, radius);
// 示例3:PostGIS圓形範圍查詢
wrapper.apply("ST_DWithin(geom, ST_SetSRID(ST_MakePoint({0}, {1}), 4326)::geography, {2})", centerLng, centerLat, radius);
2. 多條件組合查詢(前端篩選)
java運行
/**
* WebGIS前端篩選參數
*/
@Data
public class GisPointQuery {
private String name; // 點位名稱
private String[] types; // 點位類型數組
private LocalDateTime startTime; // 創建開始時間
private LocalDateTime endTime; // 創建結束時間
private Double minLng; // 最小經度
private Double maxLng; // 最大經度
}
// 條件構造
public List<GisPoint> queryGisPoint(GisPointQuery query) {
LambdaQueryWrapper<GisPoint> wrapper = new LambdaQueryWrapper<>();
// 名稱模糊查詢
wrapper.like(StringUtils.isNotBlank(query.getName()), GisPoint::getName, query.getName());
// 類型IN查詢
wrapper.in(ArrayUtil.isNotEmpty(query.getTypes()), GisPoint::getType, query.getTypes());
// 創建時間範圍
wrapper.ge(Objects.nonNull(query.getStartTime()), GisPoint::getCreateTime, query.getStartTime())
.le(Objects.nonNull(query.getEndTime()), GisPoint::getCreateTime, query.getEndTime());
// 經度範圍
wrapper.gt(Objects.nonNull(query.getMinLng()), GisPoint::getLng, query.getMinLng())
.lt(Objects.nonNull(query.getMaxLng()), GisPoint::getLng, query.getMaxLng());
// 排序
wrapper.orderByDesc(GisPoint::getCreateTime);
return gisPointService.list(wrapper);
}