MyBatis-Plus(MP)的條件構造器是其核心亮點之一,用於動態拼接 SQL WHERE 條件,替代手寫複雜的 SQL 字符串,支持類型安全、鏈式調用,大幅提升查詢開發效率。本文從「核心 API、分類使用、高級技巧、避坑指南」四個維度,全面講解條件構造器的使用。

一、核心概念與分類

1. 條件構造器體系

MP 提供多種條件構造器,核心繼承關係:

plaintext

AbstractWrapper (抽象父類)
├─ QueryWrapper (查詢/刪除條件構造器)
├─ UpdateWrapper (更新條件構造器)
└─ LambdaQueryWrapper/LambdaUpdateWrapper (Lambda版,類型安全)

構造器類型

特點

適用場景

QueryWrapper

基於字段名字符串拼接條件,靈活但無類型校驗

快速原型、簡單條件

LambdaQueryWrapper

基於 Lambda 表達式(類::方法),編譯期校驗字段名,避免拼寫錯誤

生產環境、複雜條件、類型安全

UpdateWrapper

專用於 UPDATE 語句,支持 SET 字段 + WHERE 條件

更新操作

LambdaUpdateWrapper

Lambda 版 UpdateWrapper,類型安全

生產環境更新操作

2. 核心依賴

只需引入 MP 核心依賴(已包含條件構造器):

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 示例

專用於更新操作,支持set字段 + where條件:

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)

適用於空間函數、複雜函數(如 PostGIS/MySQL 空間查詢):

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)

指定查詢字段,避免 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. 邏輯刪除兼容

MP 默認自動拼接邏輯刪除條件(is_deleted = 0),如需臨時忽略:

java運行

LambdaQueryWrapper<GisPoint> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(GisPoint::getId, 1)
       .ignoreLogicDelete(); // 忽略邏輯刪除,查詢已刪除數據

四、避坑指南與最佳實踐

1. 常見坑點

問題場景

原因

解決方案

字段名拼寫錯誤

QueryWrapper 手寫字符串字段名,大小寫 / 拼寫錯誤

改用 LambdaQueryWrapper,編譯期校驗

OR 條件導致邏輯錯誤

未嵌套 AND/OR,條件範圍超出預期

使用and(w -> ...)/or(w -> ...)嵌套,明確條件分組

SQL 注入風險

apply 方法直接拼接字符串(如apply("create_time = '" + time + "'")

使用apply("create_time = {0}", time)佔位符,MP 自動處理參數綁定

空值條件生效

傳入 null 到 eq 方法(如eq("name", null)),等價於name = null(永遠 false)

先判斷參數非空,或使用isNull("name")替代

分頁查詢總條數為 0

條件構造器拼接了錯誤的 WHERE 條件,或邏輯刪除過濾了所有數據

打印 SQL 日誌(配置 log-impl),檢查最終生成的 SQL

2. 最佳實踐

(1)優先使用 LambdaQueryWrapper

  • 類型安全,避免字段名拼寫錯誤;
  • 重構實體類字段時,IDE 可自動同步條件構造器中的字段;
  • 代碼可讀性更高(直接看到字段名,而非字符串)。

(2)封裝通用查詢條件

WebGIS 項目中,抽離通用條件(如邏輯刪除、數據權限):

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 日誌,調試條件

配置 MP 日誌,查看最終生成的 SQL:

yaml

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL到控制枱

(4)批量條件拼接用鏈式調用

避免多層 if-else,使用 MP 的「條件判斷 + 鏈式調用」:

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 / 註解

條件構造器適合「常規條件拼接」,如需 JOIN、複雜子查詢、多表關聯,優先使用 MyBatis XML / 註解寫原生 SQL,條件構造器僅作為補充。

五、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);
}

總結

MyBatis-Plus 條件構造器的核心是「簡化 WHERE 條件拼接,兼顧靈活性與類型安全」:

  1. 日常開發優先用LambdaQueryWrapper/LambdaUpdateWrapper,避免字段名錯誤;
  2. 簡單條件用鏈式調用,複雜條件用and/or嵌套,明確邏輯分組;
  3. WebGIS 場景中,結合數據庫空間函數(如ST_Distance_Sphere),通過apply方法實現空間查詢;
  4. 封裝通用條件構造器,抽離邏輯刪除、數據權限等通用邏輯,提升代碼複用性。