MybatisPlus Pro 來了,CURD 開發效率直接拉滿!

前言

--

說到 MyBatis,很多小夥伴都會用,但未必用得「驚艷」。

這個輕量級的持久層框架雖然靈活,但在日常的單表 CRUD 操作上,不得不面對重複程式碼多、開發效率低的現實問題。

我們來看一個典型的場景:

java 体验AI代码助手 代码解读复制代码// 原生 MyBatis:每個實體都要寫 XML,每個方法都要配 SQL
<mapper namespace="com.example.UserMapper">
    <select id="selectById" resultType="User">
        SELECT * FROM user WHERE id = #{id}
    </select>
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user(name, age) VALUES(#{name}, #{age})
    </insert>
    <update id="updateById">
        UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}
    </update>
    <delete id="deleteById">
        DELETE FROM user WHERE id = #{id}
    </delete>
    <!-- 還有分頁、列表、批量... 無窮無盡 -->
</mapper>

最近有位小夥伴回饋說:「蘇三哥,為什麼一個簡單的使用者表,我要寫一百多行的 XML?單表操作不就是增刪改查和分頁嗎,能不能一行程式碼搞定?」

說實話,聽到這話我一點都不意外。

當年我們使用傳統 MyBatis 時,第一反應也是一樣的。

但用了 MybatisPlus 一段時間,發現它把我們從 XML 地獄裡解救出來,確實爽。

不過日子久了,又開始覺得不夠用了,每個介面還得寫個 Controller、Service、ServiceImpl……雖然比 MyBatis 強了不少,但還是有不少重複勞動。

直到最近,我深度體驗了基於 MybatisPlus 抽取的 MybatisPlus Pro——只需繼承一個 BaseController,增刪改查、分頁、列表、排序、條件查詢,全部開箱即用。

今天這篇文章就專門跟大家一起聊聊 MybatisPlus Pro,希望對你會有所幫助。

更多專案實戰在我的技術網站:susan.net.cn/project

一、為什麼我們需要 MybatisPlus Pro?

在深入講解之前,我們先來盤點一下日常開發中那些「看似能忍、實則很煩」的場景。

痛點 1:MybatisPlus 的 BaseMapper 已經很方便了,但 Service 層還得重複寫。

每個 Service 介面基本都是在定義 findById、findAll、insert、update、delete 等通用方法。

雖然 MP 提供了 IService 介面,但依然需要在每個 ServiceImpl 裡呼叫 BaseMapper 的方法,沒有從根本上解決重複勞動的底層邏輯

痛點 2:Controller 層還是得寫。

很多小夥伴在工作中可能有這樣的經歷:需求方說要加一個新模組,需要做一套完整的增刪改查介面。

咱們開始複製貼上,改類名、改路徑、改參數……弄完一兩個模組還沒啥,等需求方一次性提了七八個模組的需求時,光寫這些「複製-貼上-微調」的程式碼就要一兩個小時,而且極其容易出錯。

痛點 3:程式碼品質參差不齊。

同樣的增刪改查,張三這樣寫,李四那樣寫。

有的參數校驗做得嚴絲合縫,有的漏了異常處理,線上直接報錯。

痛點 4:條件查詢程式碼重複率高。

每個 Controller 裡都要對 QueryWrapper 進行各種 eq、like、orderBy 的設定,邏輯極其相似,寫多了就會看到大量的重複程式碼。

有一句話說得好:最好的程式碼是沒有程式碼。

如果能把這些幾乎一模一樣的程式碼全部自動化生成,我們就能把 90% 的精力釋放出來,投入到真正的業務邏輯裡去。

二、MybatisPlus Pro 是什麼?

MybatisPlus Pro 是在 MybatisPlus 的基礎上進行增強的工具,它的核心理念是「站在巨人的肩膀上」,只做增強不做改變。

從專案結構上看,MybatisPlus Pro 就是在 MybatisPlus 的基礎上增加更多的模板功能,利用 Spring Boot 進行開發,將通用功能抽取出來,形成一套開箱即用的基類體系。

說白了,MybatisPlus Pro 的核心思路就是:在 MybatisPlus 已經「消滅」了 Mapper 層程式碼的基礎上,繼續「消滅」Service 層和 Controller 層的重複程式碼,讓開發者只需要關注核心的業務邏輯。

這裡需要說明一下,MybatisPlus Pro 目前有兩種理解:一種是社群基於 MP 二次封裝的專案實作方案(就像富貴同學那個版本),另一種是 MybatisPlus 官方在不斷迭代中新增的 Pro 級特性。本文重點圍繞前者的設計思路和實戰價值展開。

下面,我從架構師視角給大家畫一張架構圖,方便理解 MyBatis、MybatisPlus 和 MybatisPlus Pro 這三者之間的關係:

image.png

三、快速上手

說了這麼多理論,咱們直接上程式碼,感受一下 Pro 版的威力。

第一步:引入依賴

MybatisPlus Pro 建立在 MybatisPlus 基礎上,所以首先要引入 MybatisPlus 的 starter 依賴:

xml 体验AI代码助手 代码解读复制代码<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.15</version>
</dependency>

官方在 2025 年 11 月 30 日發布了 v3.5.15 版本,支援 Spring Boot 4.0.0 和 Jackson 3.0,推薦使用最新的穩定版本。

第二步:編寫工具類

工具類是 Pro 版的核心基礎設施,它提供了三個關鍵功能:

  1. 駝峰與底線的互相轉換——解決 Java 實體類欄位名和資料庫欄位名的自動對映問題
  2. 透過反射取得實體欄位值——動態提取實體中各欄位的值,為構建 QueryWrapper 做準備
  3. 基於實體類自動生成 QueryWrapper——零程式碼構建查詢條件,大大簡化條件查詢的撰寫

這裡的關鍵邏輯需要詳細講一下:當一個實體物件(比如 User 物件)傳入 getQueryWrapper 方法時,這個方法會透過 Java 反射取得這個類的所有宣告欄位(用 entity.getClass().getDeclaredFields() 取得)。

然後遍歷每一個欄位,跳過那些被 final 修飾的欄位(因為這些欄位不能動態修改),再透過 field.setAccessible(true) 突破 Java 的存取權限限制,取得被 private 修飾的欄位值。

如果欄位值不為 null,就把 Java 的駝峰欄位名(如 userName)轉換成資料庫的底線欄位名(如 user_name),然後向 QueryWrapper 新增一個 eq(等值匹配)條件。

這樣一來,我們傳入的實體裡有哪些非空欄位,QueryWrapper 就自動用這些欄位作為查詢條件,完全不需要手動編寫。

java 体验AI代码助手 代码解读复制代码public class ApprenticeUtil {
    private static Pattern humpPattern = Pattern.compile("[A-Z]");
    private static Pattern linePattern = Pattern.compile("_(\\w)");

    // 駝峰轉底線(userName -> user_name)
    public static String humpToLine(String str) {
        Matcher matcher = humpPattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    // 底線轉駝峰(user_name -> userName)
    public static String lineToHump(String str) {
        str = str.toLowerCase();
        Matcher matcher = linePattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    // 根據非空欄位生成 QueryWrapper——這裡是核心
    public static <E> QueryWrapper<E> getQueryWrapper(E entity) {
        Field[] fields = entity.getClass().getDeclaredFields();
        QueryWrapper<E> eQueryWrapper = new QueryWrapper<>();
        for (Field field : fields) {
            // 忽略 final 欄位
            if (Modifier.isFinal(field.getModifiers())) {
                continue;
            }
            field.setAccessible(true);
            try {
                Object obj = field.get(entity);
                if (obj != null) {
                    String name = humpToLine(field.getName());
                    eQueryWrapper.eq(name, obj);
                }
            } catch (IllegalAccessException e) {
                return null;
            }
        }
        return eQueryWrapper;
    }
}

第三步:編寫通用 BaseController

BaseController 是整個 Pro 版的「引擎」,它整合了最核心的 CRUD 操作邏輯:

java 体验AI代码助手 代码解读复制代码public class BaseController<S extends IService<T>, T> {
    @Autowired
    protected S baseService;

    @PostMapping("add")
    public Result add(@RequestBody T entity) {
        return Result.success(baseService.save(entity));
    }

    @PostMapping("update")
    public Result update(@RequestBody T entity) {
        return Result.success(baseService.updateById(entity));
    }

    @GetMapping("delete")
    public Result delete(String id) {
        return Result.success(baseService.removeById(id));
    }

    @GetMapping("detail")
    public Result detail(String id) {
        return Result.success(baseService.getById(id));
    }

    @PostMapping("list")
    public Result list(@RequestBody T entity) {
        // 利用工具類自動構建查詢條件
        QueryWrapper<T> wrapper = ApprenticeUtil.getQueryWrapper(entity);
        if (wrapper == null) {
            wrapper = new QueryWrapper<>();
        }
        return Result.success(baseService.list(wrapper));
    }

    @PostMapping("page")
    public Result page(@RequestBody PageDto<T> pageDto) {
        T entity = pageDto.getEntity();
        QueryWrapper<T> wrapper = ApprenticeUtil.getQueryWrapper(entity);
        if (wrapper == null) {
            wrapper = new QueryWrapper<>();
        }
        IPage<T> page = new Page<>(pageDto.getPageNo(), pageDto.getPageSize());
        return Result.success(baseService.page(page, wrapper));
    }
}

這段程式碼的底層執行邏輯值得詳細剖析。

當客戶端透過/add介面送出 POST 請求時,Spring 框架自動將 JSON 格式的請求體解析為 T 類型的實體物件。

然後 baseService.save(entity) 被呼叫,baseService 實際上是 MybatisPlus 的 IService 介面的實作類,它內部持有一個 BaseMapper。

save 方法會自動判斷:如果主鍵為 null,執行 insert 插入操作;如果主鍵不為 null,執行 updateById 更新操作。

MybatisPlus 在應用啟動時就會自動向 Mapper 中注入這些基本的 CRUD 操作,所以不需要我們寫一行 SQL。

這裡特別說一下 list 方法:參數是 T 類型實體,方法內部直接用 ApprenticeUtil.getQueryWrapper(entity) 自動構建 QueryWrapper。

比如前端傳入一個只有 name 為「張三」的 User 物件(其他欄位為 null),那麼框架會自動生成 WHERE name = '張三' 的查詢條件。

所有非空欄位都會被納入查詢條件,自動取交集。

這就實現了一個強大的能力——給什麼欄位查什麼欄位,完全零程式碼

第四步:實際使用

搞定了 BaseController,我們再來建立一個 ProductController,看看程式碼量能減少多少:

java 体验AI代码助手 代码解读复制代码// 實體類
@Data
@TableName("product")
public class Product {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private BigDecimal price;
    private Integer stock;
    private LocalDateTime createTime;
}

// Service 介面
public interface ProductService extends IService<Product> {
}

// Service 實作(按常規寫法,也可以自動生成)
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
}

// Controller——就這一行!
@RestController
@RequestMapping("/product")
public class ProductController extends BaseController<ProductService, Product> {
}

你沒看錯,建立一個完整 CRUD 的 Controller 只需要繼承 BaseController

增刪改查、分頁、列表……這些介面全部自動生成,一條程式碼都不用寫!

四、底層原理深度剖析

很多小夥伴可能會好奇:BaseController 裡的這些方法是如何知道要對哪張表操作的呢?

為什麼我寫一個方法,對應的 SQL 就自動生成了?

這個問題的答案藏在 MybatisPlus 的底層執行鏈路裡。

4.1 完整的 SQL 執行鏈路

我們用一張清晰的流程圖來展示從 Controller 呼叫到資料庫返回結果的全過程:

image.png

4.2 重點剖析:BaseMapper 的注入機制

這裡我來重點講一下 MybatisPlus 的核心魔法是如何實現的。

很多小夥伴可能每天都在用 BaseMapper,但從來沒想過它是怎麼生效的。

先回顧一下 MyBatis 的核心流程:MyBatis 透過 SqlSessionFactoryBuilder 讀取 mybatis-config.xml 設定檔,構建 SqlSessionFactory,再由 SqlSessionFactory 建立 SqlSession,每次操作資料庫都透過 SqlSession 完成。

MyBatis 底層自訂了 Executor 執行器介面來操作資料庫,Executor 有兩個主要實作:一個是基本執行器(SimpleExecutor),一個是快取執行器(CachingExecutor)。

MybatisPlus 的強大之處在於它在這個基礎上做了增強:

1. 動態代理機制:在 Spring 啟動時,所有繼承了 BaseMapper 的介面都會被 MapperProxyFactory 使用 JDK 動態代理建立代理物件。當你呼叫 productMapper.selectById(1L) 時,實際上呼叫的是 MapperProxy 的 invoke 方法,這個方法會攔截你的介面呼叫,分析出你要執行的是一個名為 selectById 的查詢操作。

2. MappedStatement 的自動生成:MybatisPlus 的 SQL 注入器(SqlInjector)會在應用啟動時,為 BaseMapper 中的所有通用方法生成對應的 MappedStatement。

MappedStatement 是 MyBatis 的底層封裝物件,它包含了 SQL 模板、參數型別、回傳結果型別等完整的 SQL 執行資訊,mapper.xml 檔案中一個 SQL 操作就對應一個 MappedStatement。

MP 在啟動時自動將這些 SQL 注入到 MyBatis 的 Configuration 中,這就是為什麼我們不需要寫任何 XML 就能執行 SQL 的原因。

3. 攔截器鏈(Interceptor Chain):MybatisPlus 透過 MybatisPlusInterceptor 建構了一个攔截器鏈,在 Executor 執行前注入各種增強邏輯,比如分頁、樂觀鎖、多租戶等,這些功能都是透過攔截器和責任鏈模式來實現的。

4. 完整的方法解析機制:當你的 Mapper 介面呼叫通用方法時,MybatisPlus 能知道這是通用方法還是自訂方法,並找到對應的 MappedStatement 來執行 SQL。

4.3 條件構造器(Wrapper)的工作原理

以條件查詢為例,下面是條件構造器內部執行的詳細流程:

image.png

Lambda 表達式的核心優勢是型別安全——使用 User::getName 而不是字串 "name",如果欄位名寫錯了,編譯器立刻就會報錯,而傳統的字串寫法只有到執行時才會發現 SQL 錯誤,這是技術上對「慣例優於設定」理念的一種實現。

五、優缺點

5.1 優點

1. 開發效率呈指數級提升一個模組從介面定義到 CRUD 功能完整可用,只需要繼承 BaseController 並注入 Service,5 行程式碼搞定。相比原生 MyBatis 要寫 XML、Mapper、Service、ServiceImpl、Controller,工作量減少 80% 以上。

2. 程式碼風格統一,降低維護成本所有模組的增刪改查邏輯全部來自同一個 BaseController,程式碼風格完全一致。新人接手專案時,不需要去研究每個 Controller 用了什麼技巧——所有的結構和處理方式都一樣。

3. 零 XML 設定MybatisPlus 本身已經做到了不需要 XML 設定 BaseCRUD,而 Pro 版進一步把 Controller 層的設定也省掉了。

4. 功能完備,開箱即用分頁外掛已經內建,只需要傳 pageNo 和 pageSize 就可以獲得完整的分頁查詢結果。條件構造器可以靈活構建複雜的查詢條件,比如 eq(等於)、like(模糊匹配)、between(範圍查詢)、orderBy(排序)等,可以滿足絕大多數業務場景的需求。

5. 非侵入式設計MybatisPlus Pro 是在 MybatisPlus 基礎上做的增強,你仍然可以在需要複雜 SQL 的地方編寫原生 MyBatis 的 Mapper 和 XML,兩者完全相容。

5.2 缺點

1. 複雜多表關聯查詢仍然是痛點一旦涉及三張表以上的關聯查詢,條件構造器會讓程式碼變得非常複雜和難以閱讀。有經驗的小夥伴應該能體會到:用 Wrapper 寫三表關聯,程式碼的邏輯可能比寫 SQL 還要繞。原因在於 Wrapper 本身設計是用來構建單表查詢條件的,它並不擅長處理多表 JOIN 的語義表達。

2. 部分場景存在性能隱患需要特別注意的是,BaseMapper 自帶的 selectById、selectList 等方法預設會查詢所有欄位——相當於 SQL 中的 SELECT *。在 MySQL 效能優化中,有一條黃金法則:不要使用 SELECT *,因為這會增加不必要的網路傳輸和記憶體開銷,尤其是當表中包含 TEXT、BLOB 這類大欄位時,性能問題更加明顯。

官方在使用說明中也提醒:MP 會對啟動即自動注入基本 CRUD 操作,性能損耗雖然極小,但在極端場景下需要謹慎考量。

3. 一些方法存在魔法值隱患比如使用 QueryWrapper<User>().eq("id", 1) 這種方式,欄位名是以字串形式寫死的。如果資料庫欄位名改了,編譯階段根本無法發現,只有執行到那一行程式碼時才會報錯,排查起來非常耗時。這也是為什麼官方越來越推崇 LambdaWrapper 的原因——用方法引用代替字串,從根本上解決這個問題。

4. 部分功能有過度封裝之嫌有開發者認為 MP 一定程度上「侵入」了 Service 層,當業務邏輯變得非常複雜時,這些封裝好的通用方法反而可能成為限制。

5. SQL 可讀性和調校難度增加當使用複雜的條件構造器時,生成的 SQL 語句對除錯來說不夠直觀。如果不熟悉 MP 的內部機制,定位慢查詢的原因會比直接看 XML 設定的 SQL 更困難。

六、使用場景與選型建議

6.1 推薦使用 MybatisPlus Pro 的場景

場景 1:中小型專案、快速迭代專案這類專案的特點是需求變化快,對開發速度要求高,但對極致性能要求相對較低。使用 MybatisPlus Pro 可以大幅提升開發效率。

場景 2:後台管理系統、管理後台類應用這些應用的核心功能就是圍繞資料的增刪改查,模組數量多、業務邏輯相對簡單。Pro 版特別適合這類場景。

場景 3:基礎 CRUD 操作占主導的專案如果專案中 80% 以上都是單表增刪改查操作(比如各種設定管理、字典管理、基礎資料維護),Pro 版能帶來巨大的效率提升。

場景 4:開發團隊規模較小,希望統一程式碼風格小團隊或者個人開發者,用 Pro 版可以快速搭建專案,同時保證程式碼結構清晰統一。

6.2 謹慎使用或不推薦的場景

場景 1:業務核心、對 SQL 執行效率要求極高的系統比如交易系統、支付系統、高併發的訂單系統等,需要對每一條 SQL 的性能做極致調優。MP 的通用方法(尤其是 SELECT *)可能會成為性能瓶頸。

場景 2:複雜報表系統、複雜資料統計系統這類系統往往需要撰寫十幾行甚至幾十行的複雜 SQL,多表關聯、子查詢、聚合函數比比皆是。這種情況下,直接編寫原生 SQL 比使用條件構造器更清晰、更可控。

場景 3:對 SQL 需要精細審核的安全敏感系統在一些金融、政務等對 SQL 語句需要逐條審查的場景下,框架自動生成的 SQL 可能不太符合審核要求。此時建議全部使用手寫的 XML SQL。

6.3 架構選型時的對比參考

下面這張表幫大家快速理清 MyBatis、MybatisPlus 和 MybatisPlus Pro 之間的差異,做到「選型有據」:

維度原生 MyBatis MybatisPlus MybatisPlus Pro CRUD 開發速度 慢(需手寫 SQL) 快(BaseMapper) 極快(全自動) 程式碼量 多 較少 很少 多表 SQL 靈活性 高(完全可控) 高(相容原生) 高(相容原生) 學習成本 低 中等 中等 客製化程度 高 高 高 統一程式碼風格 差 一般 好 適用專案 所有 所有 中大型 + 快速開發 對資料庫的掌控力 完全掌控 單表可封裝 單表可封裝 > 這個對比表幫大家在技術選型時做到心裡有數——每個框架都有自己的優勢和侷限,關鍵是根據專案的實際情況來做選擇。

七、避坑指南

作為過來人,我踩過不少坑,下面分享幾點實戰經驗:

坑 1:不要過度使用通用查詢方法

有小夥伴在工作中喜歡「一個 selectList 走天下」,這樣雖然方便,但遇到高頻查詢的介面時,SELECT * 查詢所有欄位會嚴重影響性能。建議重要的查詢介面一定要用 select() 指定欄位。

坑 2:Lambda 版本優先於字串版本

使用 LambdaQueryWrapper 代替 QueryWrapper,透過方法引用的方式避免硬編碼字串,既保證了型別安全,又避免了欄位名變更時的隱性錯誤。

java 体验AI代码助手 代码解读复制代码// ✅ 推薦寫法(型別安全)
userMapper.selectList(Wrappers.<User>lambdaQuery()
    .eq(User::getStatus, 1)
    .like(User::getUsername, "zhang")
    .orderByDesc(User::getCreateTime));

// ❌ 不推薦寫法(字串容易拼錯)
userMapper.selectList(Wrappers.<User>query()
    .eq("status", 1)
    .like("username", "zhang"));

坑 3:分頁一定要用 MybatisPlus 的分頁外掛

不要自己寫 LIMIT,MybatisPlus 內建的分頁外掛基於物理分頁,對不同資料庫提供了良好的相容性,不僅程式碼簡潔,性能也好很多。

坑 4:複雜 SQL 果斷放棄 Wrapper,回歸原生

當查詢涉及三張表以上的關聯時,我的建議是:不要掙扎,直接在 Mapper XML 裡寫 SQL。清晰、可控、易於調優,這才是真正的生產力。

坑 5:實體欄位命名遵循駝峰規範

MP 的自動對映依賴駝峰命名規範,建議始終使用標準的駝峰命名,避免不必要的設定。

總結

寫到這裡,我們來簡單總結一下。

MybatisPlus Pro 的出現,核心目標是解決一個根本問題:把開發者從海量的重複程式碼中解放出來。

它繼承了 MybatisPlus 無侵入、高效率的設計理念,透過 BaseController 這一層巧妙的設計,把 Controller 層、Service 層的重複勞動全部自動化。

配合工具類的反射機制和 Wrapper 條件構造器,還可以實現基於實體非空欄位的自動條件查詢——開發體驗可以說是質的飛躍。

當然,沒有銀彈

Pro 版在複雜多表關聯、極致性能優化等場景下確實存在短板,但正如業界公認的:它解決了 80% 的簡單 CRUD 重複工作,剩下的 20% 複雜場景依然能手寫 SQL,兩者結合才是最佳實踐。

MyBatis-Plus 在 MyBatis 的基礎上只做增強不做改變,既簡化了開發,又保留了 MyBatis 的所有靈活性。

最後,用一句話與所有開發者共勉:提高效率不是偷懶,而是把精力投入到更有價值的業務中去。

如果覺得有用,點個在看支援一下!

更多專案實戰在我的技術網站:susan.net.cn/project


原文出處:https://juejin.cn/post/7646606448858595364


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝6   💬2   ❤️1
179
🥉
我愛JS
💬1  
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登