MyBatis 作為金融 IT 領域事實標準的持久層框架,以輕量、SQL 掌控力強著稱。但深入源碼與實戰後,會發現其設計中存在多個值得商榷甚至稱為「缺陷」的決策。本文從架構視角剖析六大核心問題。
現狀:MyBatis 3.x 仍使用 DTD (Document Type Definition) 驗證 XML 配置(<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ...>)。
痛點:
影響:自定義標籤擴展極其困難,第三方整合需繞過驗證或犧牲 IDE 支援。
案例:3.3.0 → 3.4.0(次版本號升級,按語義化版本應保證向下兼容)
// 3.3.0
Statement prepare(Connection connection) throws SQLException;
// 3.4.0 - 直接在原方法新增參數!
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
後果:
StatementHandler 介面的自定義插件/擴展直接編譯報錯NoSuchMethodError,極難排查ResultSetHandler、Executor 等核心介面均有)建議:遵循「介面不變、新增介面」原則,或提供適配器模式平滑過渡。
現狀:單一 Interceptor 介面 + @Intercepts + @Signature 註解攔截多組件多方法。
缺陷:
StatementHandler、ResultSetHandler、ParameterHandler 等不同關注點的攔截邏輯塞進同一類@Signature 註解中的型別/方法名/參數列表均為字串,拼寫錯誤編譯通過,啟動時才報錯更好設計:針對每個組件定義專用擴展介面(StatementHandlerPlugin、ResultSetHandlerPlugin),編譯期強類型檢查。
現狀:無論是否啟用二級緩存、是否命中、是否更新,CachingExecutor 都會無條件計算 CacheKey(包含 SQL、參數、RowBounds 等雜湊)。
代碼證據:
// CachingExecutor.query()
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 之後才判斷 cache.getObject(key)...
影響:高併發下,大量無意義的雜湊計算佔用 CPU,且緩存本身設計簡陋(無分區、無淘汰策略配置、序列化開銷大),實際生產多建議關閉,改用 Redis/Caffeine。
JDBC 原生行為:同一 Connection、同一事務中,可交替執行 executeUpdate() 和 addBatch()/executeBatch(),回滾正常。
MyBatis 限制:SqlSession 內部維護 ExecutorType(SIMPLE/REUSE/BATCH),同一事務不可切換執行模式。嘗試混合會拋異常:Cannot change executor type within a transaction。
場景痛點:主表單條插入 + 明細批量插入,被迫拆分事務或放棄 Batch 模式。
現狀:MyBatis 宣稱「SQL 由開發者掌控,兼容性由開發者負責」。
反駁:優秀框架應提供輔助兼容能力而非完全放任:
DECODE → 標準 CASE WHEN 自動轉換標籤LIMIT / Oracle ROWNUM / PostgreSQL OFFSET/FETCH 統一標籤核心矛盾:又回到問題 1——擴展性極差(DTD、插件機制),導致使用者無法優雅擴展兼容層。
| 缺陷 | 嚴重程度 | 規避/改進方案 |
|---|---|---|
| DTD 配置 | 中 | 接受現狀,期待 4.0 XSD 支援 |
| 版本兼容 | 高 | 鎖定版本、升級前跑全量測試、避免實現核心介面 |
| 插件機制 | 中 | 單一職責拆分類、單元測試覆蓋插件邏輯 |
| 緩存無效 | 低 | 關閉二級緩存,接入 Redis/Caffeine |
| 批量限制 | 中 | 拆分 SqlSession、或手動管理 JDBC Batch |
| DB 兼容 | 高 | 自建方言模組、SQL 模板引擎、或遷移 MyBatis-Plus |
結語:MyBatis 以「簡單、直接、掌控 SQL」贏得市場,但其架構決策中的歷史包袱(DTD、單一 Interceptor、緩存設計)在現代雲原生、高併發、多數據庫場景下顯得力不從心。理解其缺陷,不是為了否定,而是為了在技術選型、架構演進、踩坑避雷時做出更理性的判斷。