MyBatis-Plus 核心组件关系:BaseMapper、IService、QueryWrapper、LambdaQueryWrapper 大白话讲解
一、先看一张关系图(心中有数)
┌──────────────────────────────────────────────────────┐ │ IService<T> │ │ (Service层接口,继承自...谁?往下看) │ │ │ │ 内部持有一个 BaseMapper<T>,给它加了很多便捷方法 │ │ 比如 saveBatch()、saveOrUpdate() 等批量/链式操作 │ └──────────────┬───────────────────────────────────────┘ │ 包装 / 增强 ▼ ┌──────────────────────────────────────────────────────┐ │ BaseMapper<T> │ │ (Mapper层接口,最底层的数据库操作) │ │ │ │ 方法签名长这样: │ │ selectList(QueryWrapper<T> wrapper) │ │ selectOne(QueryWrapper<T> wrapper) │ │ update(T entity, QueryWrapper<T> wrapper) │ │ delete(QueryWrapper<T> wrapper) │ └──────────────┬───────────────────────────────────────┘ │ 方法参数用的是 ▼ ┌──────────────────────────────────────────────────────┐ │ AbstractWrapper<T, R, Children> │ │ (抽象父类) │ │ / \ │ │ QueryWrapper<T> LambdaQueryWrapper<T> │ │ (字符串写列名) (Lambda写列名) │ │ │ │ eq("name", "张三") eq(User::getName, "张三") │ │ 容易写错,编译不报错 IDE帮你检查,不怕字段改名 │ └──────────────────────────────────────────────────────┘二、BaseMapper — 最底层的干活的
一句话:BaseMapper 就是你和数据库之间的"直通车",定义最基本的增删改查方法。
// 你写的接口publicinterfaceUserMapperextendsBaseMapper<User>{// 不用写任何代码,BaseMapper 自带了:// insert()、deleteById()、updateById()、selectById()// selectList()、selectPage() 等等}你需要知道的重点:
- BaseMapper 里那些带条件的方法(
selectList、update、delete等),参数都接收一个 Wrapper。 - Wrapper 就是"条件构造器",用来告诉数据库:我要查/改/删哪些记录?
- 你不传 Wrapper → 查全表;传了 Wrapper → 按条件筛选。
// 不传条件 → 查全表userMapper.selectList(null);// 传条件 → 只查名字叫"张三"的userMapper.selectList(newQueryWrapper<User>().eq("name","张三"));三、IService — BaseMapper 的"加装套餐"
一句话:IService 不干新活,它只是把 BaseMapper 包装了一下,让你用起来更爽。
// 你的 Service 接口publicinterfaceUserServiceextendsIService<User>{}// 你的 Service 实现类@ServicepublicclassUserServiceImplextendsServiceImpl<UserMapper,User>// ← 这行绑定了 BaseMapperimplementsUserService{}IService 比 BaseMapper 多出来什么?
| BaseMapper 的方法 | IService 多出来的方法 |
|---|---|
insert(T entity) | saveBatch(Collection<T>)批量插入 |
updateById(T entity) | saveOrUpdate(T)有ID就更新,没ID就插入 |
selectList(Wrapper) | list()直接查全表,不用传 null 了 |
返回int(影响行数) | save()返回boolean(成功/失败) |
本质上:ServiceImpl里面持有一个BaseMapper,IService 所有的批量/便捷方法,底层还是调的 BaseMapper。只是帮你省了循环和判断的代码。
// 用 BaseMapper:你得手动判断Useruser=userMapper.selectById(1L);if(user==null){userMapper.insert(newUser);}else{userMapper.updateById(newUser);}// 用 IService:一行搞定userService.saveOrUpdate(newUser);四、QueryWrapper — 用字符串拼条件(传统方式)
一句话:把 SQL 的 WHERE 条件用 Java 代码拼出来,列名写成字符串。
QueryWrapper<User>wrapper=newQueryWrapper<>();wrapper.eq("name","张三")// WHERE name = '张三'.like("email","@qq.com")// AND email LIKE '%@qq.com%'.gt("age",18)// AND age > 18.orderByDesc("create_time");// ORDER BY create_time DESCList<User>users=userMapper.selectList(wrapper);缺点很明显:
// 字段名写错了,编译不报错,运行时才发现wrapper.eq("naem","张三");// 应该是 "name",写成了 "naem"// 数据库字段改名了,要全局搜索字符串,容易漏掉wrapper.eq("create_time",...);// 如果改成了 gmt_create,这里不会提示五、LambdaQueryWrapper — 用 Lambda 写列名(推荐方式)
一句话:跟 QueryWrapper 干一样的事,但列名用实体类::get方法来写,编译期就能检查错误。
LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.eq(User::getName,"张三")// 用 Lambda 引用字段.like(User::getEmail,"@qq.com").gt(User::getAge,18).orderByDesc(User::getCreateTime);List<User>users=userMapper.selectList(wrapper);优点:
// 字段名写错了?IDE 直接标红,编译都过不了wrapper.eq(User::getNaem,"张三");// 编译报错!// 字段改名了?用 IDE 的重构功能一键改名,所有引用自动更新它怎么做到的?
Lambda 表达式User::getName在 Java 里是一种叫SFunction的东西,MyBatis-Plus 会从里面解析出字段名"name",然后再拼 SQL。这个过程叫“序列化 Lambda 解析”。
六、他们之间的调用关系(用代码串一遍)
// ==================== 第 1 步:定义实体 ====================@Data@TableName("user")publicclassUser{privateLongid;privateStringname;privateIntegerage;privateStringemail;}// ==================== 第 2 步:定义 Mapper(继承 BaseMapper) ====================publicinterfaceUserMapperextendsBaseMapper<User>{}// ==================== 第 3 步:定义 Service(继承 IService) ====================publicinterfaceUserServiceextendsIService<User>{}@ServicepublicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsUserService{// ServiceImpl 内部自动绑定了 UserMapper}// ==================== 第 4 步:在 Controller 里用 ====================@RestControllerpublicclassUserController{@AutowiredprivateUserServiceuserService;// IService 层@AutowiredprivateUserMapperuserMapper;// BaseMapper 层// 方式一:用 BaseMapper + LambdaQueryWrapperpublicList<User>getAdults(){LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.gt(User::getAge,18);returnuserMapper.selectList(wrapper);// BaseMapper 的方法}// 方式二:用 IService 的便捷方法(底层还是调 BaseMapper)publicList<User>getAdults2(){LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.gt(User::getAge,18);returnuserService.list(wrapper);// IService 的方法,等价于上面}// 方式三:用 IService 的链式调用(LambdaQueryChainWrapper)publicList<User>getAdults3(){returnuserService.lambdaQuery().gt(User::getAge,18).list();// 连 Wrapper 都不用自己 new}}七、LambdaQueryChainWrapper — 最偷懒的写法
如果你用 Service 层(IService),还可以更省事——链式调用:
// 不用自己 new LambdaQueryWrapperuserService.lambdaQuery().eq(User::getName,"张三").gt(User::getAge,18).list();// 查列表userService.lambdaUpdate().eq(User::getName,"张三").set(User::getAge,20).update();// 更新这个lambdaQuery()返回的就是一个LambdaQueryChainWrapper,它帮你把LambdaQueryWrapper和BaseMapper的调用包在了一起,你只管一直.下去就行了。
八、总结:什么时候用什么?
| 场景 | 用什么 | 原因 |
|---|---|---|
| 简单单表 CRUD,不用条件 | IService的save()、getById()等 | 比 Mapper 多返回 boolean、批量操作 |
| 带条件的查询/更新/删除 | LambdaQueryWrapper | 编译期检查,字段名不怕写错 |
| 用 Service 层 + 条件查询 | .lambdaQuery().xxx.list() | 最省代码,链式一路点到底 |
| 复杂 SQL(多表 JOIN) | 直接在 Mapper.xml 写 SQL | Wrapper 只适合单表,多表老老实实写 SQL |
核心关系再捋一遍:
- BaseMapper= 数据库操作的底层接口,方法参数用 Wrapper 传条件
- IService= 包装了 BaseMapper,加了批量/链式/布尔返回值的便捷方法
- QueryWrapper= 用字符串写列名拼条件,传统但容易出错
- LambdaQueryWrapper= 用 Lambda 写列名拼条件,编译期安全,强烈推荐
- LambdaQueryChainWrapper=
lambdaQuery()返回的东西,把 Wrapper + Mapper 调用合成一条链,写起来最爽
一句话记牢:IService 包着 BaseMapper,BaseMapper 吃着 Wrapper,Lambda 是 Wrapper 的升级版(用 Lambda 代替字符串列名)。