news 2026/4/18 9:58:47

MyBatis-Plus 深度指南:从基础到实战,让 DAO 层开发效率起飞

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis-Plus 深度指南:从基础到实战,让 DAO 层开发效率起飞

1、MyBatis-Plus 简介:不止是增强,更是重构

MyBatis-Plus 可以理解为「MyBatis + 瑞士军刀皮肤 + 防删库保险栓」—— 它在保留 MyBatis 原生特性的基础上,通过 "零侵入" 设计实现了单表操作的极简开发。

其核心价值体现在三方面:

  • 瑞士军刀般的便捷性:将 XML 配置的 "青铜时代" 升级为 Lambda 表达式的 "赛博坦时代",用极简代码实现复杂操作;

  • 保险栓级的安全性:通过拦截器让 "delete from table" 这类危险操作成为不可能,从源头避免删库风险;

  • 零侵入的兼容性:无需修改现有 MyBatis 代码,老项目可平滑迁移,既保留原生 SQL 灵活性,又获得增强功能。

2、核心功能:单表操作的 "全能工具箱"

MyBatis-Plus 的核心功能是围绕单表的 CRUD 展开的,覆盖从基础操作到高级查询的全场景,所有功能均通过BaseMapper接口暴露,无需手动实现。

2.1 基础 CRUD:单表操作 "零代码" 实现

BaseMapper 中封装了单表所有基础操作,无需手写 SQL 就能满足 90% 的业务需求。关键方法整理如下:

操作类型方法示例作用注意事项

新增

int insert(T entity)

插入一条记录,返回影响行数

自动忽略 entity 中 null 值的属性

新增 / 更新

boolean insertOrUpdate(T entity)

若记录存在则更新,否则插入

依赖主键查询(建议查主库避免主从延迟)

删除

int deleteById(Serializable id)

根据主键删除单条记录

物理删除,数据恢复需 DBA 协助

删除

int deleteByIds(Collection<?> idList)

批量删除主键对应的记录

需手动控制 idList 大小,防止数据库负载过高

更新

int updateById(T entity)

根据主键更新记录

主键必须非空,忽略 null 值属性(避免 WHERE id=NULL)

查询

T selectById(Serializable id)

根据主键查询单条记录

-

查询

List<T> selectList(Wrapper<T> queryWrapper)

根据条件批量查询

queryWrapper 为 null 时会全表扫描,需谨慎

2.2 条件构造器:复杂查询 "优雅编码"

条件构造器是 MyBatis-Plus 的 "灵魂",支持用面向对象的方式构建 SQL 条件,避免字符串拼接的坑。核心实现有 4 种:

  • QueryWrapper:基础条件构造器,通过字符串指定字段(如eq("name", "张三"));

  • LambdaQueryWrapper:基于 Lambda 表达式的构造器(如eq(User::getName, "张三")),推荐优先使用;

  • UpdateWrapper/LambdaUpdateWrapper:用于构建更新条件,支持动态设置 set 值。

为什么优先用 LambdaQueryWrapper?

对比传统写法的优势一目了然:

// ❌ 不推荐:字段拼写错误编译不报错,字段变更易遗漏 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("name", "张三").gt("age", 18); // ✅ 推荐:编译期检查字段有效性,重构自动更新引用 LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(User::getName, "张三").gt(User::getAge, 18);

优势体现在:

  • 防止字段拼写错误导致的 SQL 异常(编译期校验);

  • 自动校验值类型,避免因类型不匹配导致索引失效;

  • 提高代码可读性,字段含义一目了然。

2.3 丰富的插件集合:功能扩展的 "万能接口"

MyBatis-Plus 通过MybatisPlusInterceptor实现插件机制,可拦截 SQL 执行过程并增强功能,核心插件如下:

插件名称作用关键说明
PaginationInnerInterceptor

自动分页

必须配置,否则会内存分页(不拼接 limit)

BlockAttackInnerInterceptor

防止全表更新 / 删除

拦截不带 WHERE 条件的 update/delete,避免误操作

OptimisticLockerInnerInterceptor

乐观锁

通过版本号字段解决并发更新冲突

TenantLineInnerInterceptor

多租户

自动为 SQL 添加租户 ID 条件,隔离数据

IllegalSQLInnerInterceptor

SQL 性能规范

拦截不符合规范的 SQL(如 SELECT *)

3、使用建议:写出高效、安全的 MP 代码

3.1 优先使用 Lambda 条件构造器

再强调一次:别用字符串拼条件!LambdaQueryWrapper能在编译期帮你挡住 "字段不存在"、"类型不匹配" 等一堆坑,重构时还能自动更新引用 —— 相当于给代码加了 "自动纠错"buff。

3.2 条件构造器 NULL 值处理:减少冗余代码

当查询条件含null值时,传统写法需用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);

优势:减少 if 判断、避免无效查询条件。

3.3 尽量明确 select 字段:提升查询效率

默认情况下,selectList会查询所有字段(select *),指定查询字段可利用索引覆盖、减少数据传输:

// ❌ 不推荐:全表字段查询,浪费资源 List<User> users1 = userMapper.selectList(lambdaQueryWrapper); // ✅ 推荐:只查询需要的字段 lambdaQueryWrapper.select(User::getId, User::getName, User::getAge); List<User> users2 = userMapper.selectList(lambdaQueryWrapper);

优势体现在:

  • 利用索引覆盖,避免回表查询,提升 SQL 效率;

  • 减少数据传输和序列化开销,降低数据库压力;

  • 节省内存,尤其对大表查询效果明显。

4、开发实战:从 "能用" 到 "用好" 的进阶(重点)

在实际项目中,尤其是微服务架构下,需要将dao层作为单独的服务对外提供原子能力,此时必须解决QueryWrapper在 RPC 场景的痛点,同时实现高效接入、功能扩展及安全保障。

4.1 基础能力:微服务下的查询条件封装

4.1.1 痛点与解决方案

在微服务中,若将 DAO 层封装为独立服务,直接暴露QueryWrapper存在 3 大问题:

  • QueryWrapper结构复杂,序列化 / 反序列化耗时;

  • 逻辑层需引入mybatis-plus-core依赖,易导致 Jar 包冲突;

  • 原子层升级 MP 版本时,所有调用方需同步升级,维护成本高。

解决方案:自定义QueryCondition替代QueryWrapper,作为 RPC 入参,兼顾灵活性与轻量性。

4.1.2 QueryCondition 设计

QueryCondition整合查询、排序、分页及字段选择能力,结构如下:

  • 查询条件集合(queryFieldList):封装 WHERE 子句的条件,包含字段名、匹配规则(如等于、模糊查询)、拼接方式(AND/OR);

  • 排序字段集合(orderFieldList):定义排序字段及排序方式(ASC/DESC);

  • 返回字段集合(selectFieldList):指定查询结果需返回的字段,避免select *

  • 分页参数:页码(pageNum)和每页条数(pageSize)。

// 综合查询条件类 public class QueryCondition { private List<String> selectFieldList; // 返回字段 private List<QueryField> queryFieldList; // 查询条件 private List<OrderField> orderFieldList; // 排序字段 private int pageNum; // 页码 private int pageSize; // 每页条数 }
4.1.3 类型安全的构建工具:QueryConditionBuilder

为简化QueryCondition的创建,设计QueryConditionBuilder工具类,通过 Lambda 表达式实现类型安全构建:

public class QueryConditionBuilder<T> { private List<SelectFieldBuilder<T>> selectFieldBuilderList; // 返回字段构建 private List<QueryFieldBuilder<T>> queryFieldBuilderList; // 查询条件构建 private List<OrderFieldBuilder<T>> orderFieldBuilderList; // 排序字段构建 }

使用示例

@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) // 每页条数 .build(); // 构建时校验数据类型,不匹配则抛异常 }
4.1.4 接口与实现设计
  • 接口定义:在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); // 条件查询 // 其他方法... }
  • 实现设计:在dao-service通过抽象类AbstractBaseDaoImpl继承 MyBatis-Plus 的ServiceImpl,并实现BaseDao接口,在抽象类中将自定义的QueryCondition转换成LambdaQueryWrapper再调用ServiceImpl中的方法实现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); } } // ...... 其他方法的实现 }
4.1.5 整体架构

采用分层架构实现高内聚低耦合:

kf_scaffold/ ├── dao-contract/ # 接口定义层:暴露对外RPC接口 ├── dao-service/ # 服务实现层:实现接口,依赖MyBatis-Plus ├── dao-plugin/ # 插件扩展层:自定义插件(如全表拦截) ├── dao-spring-boot-starter/ # 自动配置层:封装Starter,简化接入 └── dao-demo/ # 使用示例层:提供接入示例

4.2 快速接入:3 步实现单表 CRUD 接口

以售后单表(AssOrder)为例,快速搭建对外暴露的 CRUD 服务:

4.2.1 接口层工程(ass-dao-contract)
  1. 引入依赖

<dependency> <groupId>com.bj58.zhuanzhuan.kf</groupId> <artifactId>dao-contract</artifactId> </dependency>
  1. 定义实体

// 售后单据 public class AssOrderEntity { private Long id; // 售后单ID // 其他字段... }
  1. 定义接口:继承BaseDao,无需编写方法实现

// 售后单表的 CRUD 接口 public interface IAssOrderDao extends BaseDao<Long, AssOrderEntity> { }
4.2.2 服务层工程(ass-dao-service)
  1. 引入依赖

<dependency> <groupId>com.bj58.zhuanzhuan.kf</groupId> <artifactId>dao-spring-boot-starter</artifactId> </dependency>
  1. 配置数据源:区分主从库,实现读写分离

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
  1. 编写实现:继承AbstractBaseDaoImpl,无需手动实现方法

// 售后单表的 CRUD 接口实现 public class AssOrderDao extends AbstractBaseDaoImpl<Long, AssOrderEntity, AssOrderMapper> implements IAssOrderDao { }

效果:通过上述步骤,无需编写 SQL,即可对外提供AssOrder表的 CRUD 接口,支持通过QueryCondition进行条件查询、排序、分页等操作。

4.3 丰富 BaseMapper:扩展自定义通用方法

BaseMapper的默认方法若不满足需求(如按实体属性统计数量),可按以下步骤扩展:

4.3.1 自定义 Mapper 接口

定义MyMapper继承BaseMapper,添加自定义方法:

public interface MyMapper<T> extends BaseMapper<T> { // 按实体属性拼接AND条件统计数量 int countByEntity(T entity); }
4.3.2 注入方法实现

通过AbstractMethod构建 SQL 模板,实现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), // 计数字段 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环境中 @Configuration publicclass MybatisPlusConfig { @Bean public MySqlInjector customSqlInjector() { return new MySqlInjector(); } }
4.3.4 使用扩展方法
// 定义 UserMapper 继承自定义的 MyMapper @Mapper public interface UserMapper extends MyMapper<User> { } // 调用示例 @Test public void testCountByEntity() { User user = new User(); user.setName("张三"); // 按姓名统计 int count = userMapper.countByEntity(user); System.out.println("符合条件的用户数:" + count); }

4.4 内置插件:增强系统安全性

为避免生产环境中的误操作,在dao-plugin工程中定义了两个插件:

4.4.1 全表扫描拦截(FullTableScanInterceptor)
  • 功能:拦截无查询条件的 SQL(如select * from user),防止全表扫描导致的性能问题;

  • 场景:当QueryCondition未设置查询条件时,自动拦截并抛异常。

4.4.2 全表更新拦截(BlockFullTableOperationInterceptor)
  • 功能:拦截无更新条件的 SQL(如update user set status=0),防止全表更新;

  • 价值:避免因条件构造器错误导致的批量数据修改,从源头降低风险。

5、总结:为什么 MyBatis-Plus 值得用?

MyBatis-Plus 的核心价值在于:用最小的改造成本,实现 DAO 层开发效率的质的飞跃

  • 对开发者:减少 90% 的 CRUD 代码,用 Lambda 替代字符串拼接,从 "写 SQL" 转向 "拼条件";

  • 对系统:通过插件机制增强安全性(防删库、SQL 规范)和可扩展性(分页、多租户);

  • 对团队:降低新人上手成本,统一 DAO 层编码规范,减少因 SQL 问题导致的线上故障。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 8:54:57

AI Agent和AI Skill:AI时代的指挥官与士兵关系详解

AI Agent是具有推理、规划和记忆能力的自主决策系统&#xff0c;而AI Skill是被动的功能单元需被调用。两者关系如同指挥官与士兵&#xff0c;Agent负责思考决策&#xff0c;Skill负责具体执行。分开设计是为了解决"脑容量"有限问题&#xff0c;实现按需加载。MCP协议…

作者头像 李华
网站建设 2026/4/17 19:28:00

九大学术查重平台技术特性对比与场景化推荐

核心工具对比速览 工具名称 核心功能 处理时间 适配检测平台 特色优势 aibiye 降AIGC查重 20分钟 知网/格子达/维普 保留学术术语的AI痕迹弱化 aicheck AIGC检测降重 即时 主流学术平台 实时检测反馈精准降重 askpaper 学术AI优化 15-30分钟 高校常用系统 专…

作者头像 李华
网站建设 2026/4/17 23:44:46

[嵌入式系统-175]:伺服电机编码器的原理与其对应的电信号

伺服电机的编码器&#xff08;Encoder&#xff09; 是实现高精度位置、速度和方向反馈的核心传感器。它通过检测电机转子的旋转状态&#xff0c;向控制器提供实时反馈信号&#xff0c;构成闭环控制系统。下面详细介绍伺服电机编码器的工作原理 以及其输出的 典型电信号类型与含…

作者头像 李华
网站建设 2026/4/18 3:38:31

【开题答辩全过程】以 基于web技术的酒店信息管理系统设计与实现-为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

作者头像 李华
网站建设 2026/4/18 3:36:04

护网行动实战复盘:红蓝对抗核心技巧与落地指南

护网行动实战复盘&#xff1a;红蓝对抗核心技巧与落地指南 护网行动作为网络安全领域最贴近真实攻击场景的攻防演练&#xff0c;是检验企业安全防护体系、锤炼安全团队实战能力的“试金石”。无论是红队的渗透突破&#xff0c;还是蓝队的防御处置&#xff0c;都需要依托系统的…

作者头像 李华