MyBatis-Plus 深度指南:從基礎(chǔ)到實戰(zhàn),讓 DAO 層開發(fā)效率起飛
1、MyBatis-Plus 簡介:不止是增強(qiáng),更是重構(gòu)
2、核心功能:單表操作的 "全能工具箱"
2.1 基礎(chǔ) CRUD:單表操作 "零代碼" 實現(xiàn)
2.2 條件構(gòu)造器:復(fù)雜查詢 "優(yōu)雅編碼"
2.3 豐富的插件集合:功能擴(kuò)展的 "萬能接口"
3、使用建議:寫出高效、安全的 MP 代碼
3.1 優(yōu)先使用 Lambda 條件構(gòu)造器
3.2 條件構(gòu)造器 NULL 值處理:減少冗余代碼
3.3 盡量明確 select 字段:提升查詢效率
4、開發(fā)實戰(zhàn):從 "能用" 到 "用好" 的進(jìn)階(重點)
4.1 基礎(chǔ)能力:微服務(wù)下的查詢條件封裝
4.2 快速接入:3 步實現(xiàn)單表 CRUD 接口
4.3 豐富 BaseMapper:擴(kuò)展自定義通用方法
4.4 內(nèi)置插件:增強(qiáng)系統(tǒng)安全性
5、總結(jié):為什么 MyBatis-Plus 值得用?
1、MyBatis-Plus 簡介:不止是增強(qiáng),更是重構(gòu)
MyBatis-Plus 可以理解為「MyBatis + 瑞士軍刀皮膚 + 防刪庫保險栓」—— 它在保留 MyBatis 原生特性的基礎(chǔ)上,通過 "零侵入" 設(shè)計實現(xiàn)了單表操作的極簡開發(fā)。
其核心價值體現(xiàn)在三方面:
- 瑞士軍刀般的便捷性:將 XML 配置的 "青銅時代" 升級為 Lambda 表達(dá)式的 "賽博坦時代",用極簡代碼實現(xiàn)復(fù)雜操作;
- 保險栓級的安全性:通過攔截器讓 "delete from table" 這類危險操作成為不可能,從源頭避免刪庫風(fēng)險;
- 零侵入的兼容性:無需修改現(xiàn)有 MyBatis 代碼,老項目可平滑遷移,既保留原生 SQL 靈活性,又獲得增強(qiáng)功能。
2、核心功能:單表操作的 "全能工具箱"
MyBatis-Plus 的核心功能是圍繞單表的 CRUD 展開的,覆蓋從基礎(chǔ)操作到高級查詢的全場景,所有功能均通過 BaseMapper 接口暴露,無需手動實現(xiàn)。
2.1 基礎(chǔ) CRUD:單表操作 "零代碼" 實現(xiàn)
BaseMapper 中封裝了單表所有基礎(chǔ)操作,無需手寫 SQL 就能滿足 90% 的業(yè)務(wù)需求。關(guān)鍵方法整理如下:
操作類型 | 方法示例 | 作用 | 注意事項 |
新增 |
| 插入一條記錄,返回影響行數(shù) | 自動忽略 entity 中 null 值的屬性 |
新增 / 更新 |
| 若記錄存在則更新,否則插入 | 依賴主鍵查詢(建議查主庫避免主從延遲) |
刪除 |
| 根據(jù)主鍵刪除單條記錄 | 物理刪除,數(shù)據(jù)恢復(fù)需 DBA 協(xié)助 |
刪除 |
| 批量刪除主鍵對應(yīng)的記錄 | 需手動控制 idList 大小,防止數(shù)據(jù)庫負(fù)載過高 |
更新 |
| 根據(jù)主鍵更新記錄 | 主鍵必須非空,忽略 null 值屬性(避免 WHERE id=NULL) |
查詢 |
| 根據(jù)主鍵查詢單條記錄 | - |
查詢 |
| 根據(jù)條件批量查詢 | queryWrapper 為 null 時會全表掃描,需謹(jǐn)慎 |
2.2 條件構(gòu)造器:復(fù)雜查詢 "優(yōu)雅編碼"
條件構(gòu)造器是 MyBatis-Plus 的 "靈魂",支持用面向?qū)ο蟮姆绞綐?gòu)建 SQL 條件,避免字符串拼接的坑。核心實現(xiàn)有 4 種:
- QueryWrapper:基礎(chǔ)條件構(gòu)造器,通過字符串指定字段(如
eq("name", "張三")); - LambdaQueryWrapper:基于 Lambda 表達(dá)式的構(gòu)造器(如
eq(User::getName, "張三")),推薦優(yōu)先使用; - UpdateWrapper/LambdaUpdateWrapper:用于構(gòu)建更新條件,支持動態(tài)設(shè)置 set 值。
為什么優(yōu)先用 LambdaQueryWrapper?
對比傳統(tǒng)寫法的優(yōu)勢一目了然:
// ? 不推薦:字段拼寫錯誤編譯不報錯,字段變更易遺漏
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "張三").gt("age", 18);
// ? 推薦:編譯期檢查字段有效性,重構(gòu)自動更新引用
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getName, "張三").gt(User::getAge, 18);優(yōu)勢體現(xiàn)在:
- 防止字段拼寫錯誤導(dǎo)致的 SQL 異常(編譯期校驗);
- 自動校驗值類型,避免因類型不匹配導(dǎo)致索引失效;
- 提高代碼可讀性,字段含義一目了然。
2.3 豐富的插件集合:功能擴(kuò)展的 "萬能接口"
MyBatis-Plus 通過 MybatisPlusInterceptor 實現(xiàn)插件機(jī)制,可攔截 SQL 執(zhí)行過程并增強(qiáng)功能,核心插件如下:
插件名稱 | 作用 | 關(guān)鍵說明 |
| 自動分頁 | 必須配置,否則會內(nèi)存分頁(不拼接 limit) |
| 防止全表更新 / 刪除 | 攔截不帶 WHERE 條件的 update/delete,避免誤操作 |
| 樂觀鎖 | 通過版本號字段解決并發(fā)更新沖突 |
| 多租戶 | 自動為 SQL 添加租戶 ID 條件,隔離數(shù)據(jù) |
| SQL 性能規(guī)范 | 攔截不符合規(guī)范的 SQL(如 SELECT *) |
3、使用建議:寫出高效、安全的 MP 代碼
3.1 優(yōu)先使用 Lambda 條件構(gòu)造器
再強(qiáng)調(diào)一次:別用字符串拼條件!LambdaQueryWrapper 能在編譯期幫你擋住 "字段不存在"、"類型不匹配" 等一堆坑,重構(gòu)時還能自動更新引用 —— 相當(dāng)于給代碼加了 "自動糾錯"buff。
3.2 條件構(gòu)造器 NULL 值處理:減少冗余代碼
當(dāng)查詢條件含 null 值時,傳統(tǒng)寫法需用 if 判斷避免無效條件,MP 支持 "條件性添加",一行代碼搞定:
// ? 不推薦:大量if判斷
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(name)) {
wrapper.eq(User::getName, name);
}
if (age != null) {
wrapper.eq(User::getAge, age);
}
// ? 推薦:條件性添加,減少冗余
wrapper.eq(StringUtils.isNotBlank(name), User::getName, name)
.eq(Objects.nonNull(age), User::getAge, age);優(yōu)勢:減少 if 判斷、避免無效查詢條件。
3.3 盡量明確 select 字段:提升查詢效率
默認(rèn)情況下,selectList 會查詢所有字段(select *),指定查詢字段可利用索引覆蓋、減少數(shù)據(jù)傳輸:
// ? 不推薦:全表字段查詢,浪費資源
List<User> users1 = userMapper.selectList(lambdaQueryWrapper);
// ? 推薦:只查詢需要的字段
lambdaQueryWrapper.select(User::getId, User::getName, User::getAge);
List<User> users2 = userMapper.selectList(lambdaQueryWrapper);優(yōu)勢體現(xiàn)在:
- 利用索引覆蓋,避免回表查詢,提升 SQL 效率;
- 減少數(shù)據(jù)傳輸和序列化開銷,降低數(shù)據(jù)庫壓力;
- 節(jié)省內(nèi)存,尤其對大表查詢效果明顯。
4、開發(fā)實戰(zhàn):從 "能用" 到 "用好" 的進(jìn)階(重點)
在實際項目中,尤其是微服務(wù)架構(gòu)下,需要將dao層作為單獨的服務(wù)對外提供原子能力,此時必須解決 QueryWrapper 在 RPC 場景的痛點,同時實現(xiàn)高效接入、功能擴(kuò)展及安全保障。
4.1 基礎(chǔ)能力:微服務(wù)下的查詢條件封裝
4.1.1 痛點與解決方案
在微服務(wù)中,若將 DAO 層封裝為獨立服務(wù),直接暴露 QueryWrapper 存在 3 大問題:
QueryWrapper結(jié)構(gòu)復(fù)雜,序列化 / 反序列化耗時;- 邏輯層需引入
mybatis-plus-core依賴,易導(dǎo)致 Jar 包沖突; - 原子層升級 MP 版本時,所有調(diào)用方需同步升級,維護(hù)成本高。
解決方案:自定義 QueryCondition 替代 QueryWrapper,作為 RPC 入?yún)?,兼顧靈活性與輕量性。
4.1.2 QueryCondition 設(shè)計
QueryCondition 整合查詢、排序、分頁及字段選擇能力,結(jié)構(gòu)如下:
- 查詢條件集合(queryFieldList):封裝 WHERE 子句的條件,包含字段名、匹配規(guī)則(如等于、模糊查詢)、拼接方式(AND/OR);
- 排序字段集合(orderFieldList):定義排序字段及排序方式(ASC/DESC);
- 返回字段集合(selectFieldList):指定查詢結(jié)果需返回的字段,避免
select *; - 分頁參數(shù):頁碼(pageNum)和每頁條數(shù)(pageSize)。
// 綜合查詢條件類
public class QueryCondition {
private List<String> selectFieldList; // 返回字段
private List<QueryField> queryFieldList; // 查詢條件
private List<OrderField> orderFieldList; // 排序字段
private int pageNum; // 頁碼
private int pageSize; // 每頁條數(shù)
}4.1.3 類型安全的構(gòu)建工具:QueryConditionBuilder
為簡化 QueryCondition 的創(chuàng)建,設(shè)計 QueryConditionBuilder 工具類,通過 Lambda 表達(dá)式實現(xiàn)類型安全構(gòu)建:
public class QueryConditionBuilder<T> {
private List<SelectFieldBuilder<T>> selectFieldBuilderList; // 返回字段構(gòu)建
private List<QueryFieldBuilder<T>> queryFieldBuilderList; // 查詢條件構(gòu)建
private List<OrderFieldBuilder<T>> orderFieldBuilderList; // 排序字段構(gòu)建
}使用示例:
@Test
public void selectByQuery() {
QueryConditionBuilder<User> builder = QueryConditionBuilder.builder();
QueryCondition condition = builder
.select(User::getId, User::getName) // 選擇返回字段
.eq(User::getStatus, 1) // 相等查詢
.like(User::getName, "張") // 模糊查詢
.in(User::getType, Arrays.asList(1, 2, 3)) // 集合查詢
.orderByDesc(User::getCreateTime) // 排序
.pageNum(1) // 頁碼
.pageSize(5) // 每頁條數(shù)
.build(); // 構(gòu)建時校驗數(shù)據(jù)類型,不匹配則拋異常
}4.1.4 接口與實現(xiàn)設(shè)計
- 接口定義:在
dao-contract中定義通用接口BaseDao,對外暴露單表操作的CRUD能力
public interface BaseDao<P extends Serializable, T> {
@Master
T insert(T entity, Option... option); // 新增
@Slave
List<T> selectListByQuery(QueryCondition queryCondition, Option... option); // 條件查詢
// 其他方法...
}- 實現(xiàn)設(shè)計:在
dao-service通過抽象類AbstractBaseDaoImpl繼承 MyBatis-Plus 的ServiceImpl,并實現(xiàn)BaseDao接口,在抽象類中將自定義的QueryCondition轉(zhuǎn)換成LambdaQueryWrapper再調(diào)用ServiceImpl中的方法實現(xiàn)BaseDao中對外暴漏的所有方法:
圖片
public abstract class AbstractBaseDaoImpl<P extends Serializable, T, M extends BaseMapper<T>>
extends ServiceImpl<M, T> implements BaseDao<P, T> {
@Override
public T insert(T entity, Option... option) {
// 適配自定義配置項
this.beforeOption(option);
try {
if (Objects.nonNull(entity)) {
super.save(entity);
}
return entity;
} finally {
// 回滾自定義配置項
this.afterOption(option);
}
}
@Override
public List<T> selectListByQuery(QueryCondition queryCondition, Option... option) {
if (Objects.isNull(queryCondition)) {
return new ArrayList<>();
}
this.beforeOption(option);
try {
final Wrapper<T> queryWrapper = this.queryCondition2QueryWrapper(queryCondition);
return super.list(queryWrapper);
} finally {
this.afterOption(option);
}
}
// ...... 其他方法的實現(xiàn)
}4.1.5 整體架構(gòu)
采用分層架構(gòu)實現(xiàn)高內(nèi)聚低耦合:
kf_scaffold/
├── dao-contract/ # 接口定義層:暴露對外RPC接口
├── dao-service/ # 服務(wù)實現(xiàn)層:實現(xiàn)接口,依賴MyBatis-Plus
├── dao-plugin/ # 插件擴(kuò)展層:自定義插件(如全表攔截)
├── dao-spring-boot-starter/ # 自動配置層:封裝Starter,簡化接入
└── dao-demo/ # 使用示例層:提供接入示例4.2 快速接入:3 步實現(xiàn)單表 CRUD 接口
以售后單表(AssOrder)為例,快速搭建對外暴露的 CRUD 服務(wù):
4.2.1 接口層工程(ass-dao-contract)
- 引入依賴:
<dependency>
<groupId>com.bj58.zhuanzhuan.kf</groupId>
<artifactId>dao-contract</artifactId>
</dependency>- 定義實體
// 售后單據(jù)
public class AssOrderEntity {
private Long id; // 售后單ID
// 其他字段...
}- 定義接口:繼承
BaseDao,無需編寫方法實現(xiàn)
// 售后單表的 CRUD 接口
public interface IAssOrderDao extends BaseDao<Long, AssOrderEntity> {
}4.2.2 服務(wù)層工程(ass-dao-service)
- 引入依賴
<dependency>
<groupId>com.bj58.zhuanzhuan.kf</groupId>
<artifactId>dao-spring-boot-starter</artifactId>
</dependency>- 配置數(shù)據(jù)源:區(qū)分主從庫,實現(xiàn)讀寫分離
kf:
dao:
data-source:
master:
url: jdbc:mysql://localhost:3306/master_db
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/slave_db
driver-class-name: com.mysql.cj.jdbc.Driver- 編寫實現(xiàn):繼承
AbstractBaseDaoImpl,無需手動實現(xiàn)方法
// 售后單表的 CRUD 接口實現(xiàn)
public class AssOrderDao extends AbstractBaseDaoImpl<Long, AssOrderEntity, AssOrderMapper>
implements IAssOrderDao {
}效果:通過上述步驟,無需編寫 SQL,即可對外提供 AssOrder 表的 CRUD 接口,支持通過 QueryCondition 進(jìn)行條件查詢、排序、分頁等操作。
4.3 豐富 BaseMapper:擴(kuò)展自定義通用方法
BaseMapper 的默認(rèn)方法若不滿足需求(如按實體屬性統(tǒng)計數(shù)量),可按以下步驟擴(kuò)展:
4.3.1 自定義 Mapper 接口
定義 MyMapper 繼承 BaseMapper,添加自定義方法:
public interface MyMapper<T> extends BaseMapper<T> {
// 按實體屬性拼接AND條件統(tǒng)計數(shù)量
int countByEntity(T entity);
}4.3.2 注入方法實現(xiàn)
通過 AbstractMethod 構(gòu)建 SQL 模板,實現(xiàn) countByEntity 的邏輯:
public class CountByEntityMethod extends AbstractMethod {
private static final String SQL_TEMPLATE = "<script>%s SELECT COUNT(%s) FROM %s %s %s\n</script>";
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sql = String.format(SQL_TEMPLATE,
sqlFirst(), // 前置SQL
selectColumns(tableInfo, true), // 計數(shù)字段
tableInfo.getTableName(), // 表名
sqlWhereEntityWrapper(true, tableInfo), // WHERE條件(基于實體屬性)
sqlComment()); // 注釋
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return addSelectMappedStatementForOther(mapperClass, "countByEntity", sqlSource, Integer.class);
}
}4.3.3 注冊自定義方法
通過 SqlInjector 將自定義方法注入 MyBatis-Plus:
// 自定義注入器
public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
List<AbstractMethod> methods = super.getMethodList(mapperClass, tableInfo);
methods.add(new CountByEntityMethod()); // 添加自定義方法
return methods;
}
}
// 配置注入器,將自定義注入器放入spring環(huán)境中
@Configuration
publicclass MybatisPlusConfig {
@Bean
public MySqlInjector customSqlInjector() {
return new MySqlInjector();
}
}4.3.4 使用擴(kuò)展方法
// 定義 UserMapper 繼承自定義的 MyMapper
@Mapper
public interface UserMapper extends MyMapper<User> {
}
// 調(diào)用示例
@Test
public void testCountByEntity() {
User user = new User();
user.setName("張三"); // 按姓名統(tǒng)計
int count = userMapper.countByEntity(user);
System.out.println("符合條件的用戶數(shù):" + count);
}4.4 內(nèi)置插件:增強(qiáng)系統(tǒng)安全性
為避免生產(chǎn)環(huán)境中的誤操作,在dao-plugin工程中定義了兩個插件:
4.4.1 全表掃描攔截(FullTableScanInterceptor)
- 功能:攔截?zé)o查詢條件的 SQL(如
select * from user),防止全表掃描導(dǎo)致的性能問題; - 場景:當(dāng)
QueryCondition未設(shè)置查詢條件時,自動攔截并拋異常。
4.4.2 全表更新攔截(BlockFullTableOperationInterceptor)
- 功能:攔截?zé)o更新條件的 SQL(如
update user set status=0),防止全表更新; - 價值:避免因條件構(gòu)造器錯誤導(dǎo)致的批量數(shù)據(jù)修改,從源頭降低風(fēng)險。
5、總結(jié):為什么 MyBatis-Plus 值得用?
MyBatis-Plus 的核心價值在于:用最小的改造成本,實現(xiàn) DAO 層開發(fā)效率的質(zhì)的飛躍。
- 對開發(fā)者:減少 90% 的 CRUD 代碼,用 Lambda 替代字符串拼接,從 "寫 SQL" 轉(zhuǎn)向 "拼條件";
- 對系統(tǒng):通過插件機(jī)制增強(qiáng)安全性(防刪庫、SQL 規(guī)范)和可擴(kuò)展性(分頁、多租戶);
- 對團(tuán)隊:降低新人上手成本,統(tǒng)一 DAO 層編碼規(guī)范,減少因 SQL 問題導(dǎo)致的線上故障。
關(guān)于作者
孟建國,轉(zhuǎn)轉(zhuǎn)履約中臺研發(fā)工程師,主要負(fù)責(zé)售后業(yè)務(wù)






































