在 Java 后端开发中,如果你还在为手写单表 CRUD(增删改查)的 SQL 语句而掉头发,那么 MyBatis-Plus(简称 MP)绝对是你必须掌握的效率神器。官方对它的定位是:“只做增强不做改变,为简化开发、提高效率而生”

这条文章主要是方便自己复习用,如果有错误请在评论区帮我指正,谢谢咯~

核心原理解析:为什么不用写 SQL?

MP 底层并没有脱离 JDBC 和 MyBatis,它只是做了一套非常精妙的**“自动化封装”**。

主要依赖于三个核心技术:

1.继承机制: MP 官方提供了 BaseMapper<T>IService<T> 两个接口。你的类只要继承它们,就等于直接拥有了官方提前写好的几十个标准 CRUD 方法。

2.泛型推导: 接口后面的泛型 <T>(例如 <User>)非常关键。MP 会读取这个实体类的信息。

3.反射与动态 SQL: 当项目启动时,MP 的“SQL 注入器”会利用 Java 反射机制,扫描 <T> 对应的实体类。

  • 通过类名推导出表名(User -> user 表)。
  • 通过属性名推导出字段名(userAccount -> user_account 字段)。
  • 最终在内存中自动帮你动态拼接出标准的 SQL 语句(如 SELECT * FROM user WHERE id = ?),并注入到 MyBatis 的配置中。

Mapper 层实战

Mapper 层是最贴近数据库的一层,它继承自 BaseMapper<T>,主要负责单表的最基础操作

  1. 接口定义
1
2
3
4
5
// 继承 BaseMapper,泛型指定为 User 实体类
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 里面一行代码都不用写,但是涵盖了基础 CRUD 功能!
}
  1. 常用方法演示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 新增 (Insert)
User user = new User();
user.setUserAccount("admin");
user.setUserPassword("123456");
userMapper.insert(user); // 插入成功后,雪花算法生成的 ID 会自动回填到 user 对象中

// 2. 删除 (Delete)
userMapper.deleteById(1001L); // 根据 ID 删除
userMapper.deleteBatchIds(Arrays.asList(1L, 2L, 3L)); // 批量删除

// 3. 修改 (Update)
User updateObj = new User();
updateObj.setId(1001L);
updateObj.setUserName("新名字");
userMapper.updateById(updateObj); // 仅更新非空字段,密码等未设置的字段不会被覆盖

// 4. 查询 (Select)
User dbUser = userMapper.selectById(1001L); // 查单条
List<User> userList = userMapper.selectBatchIds(Arrays.asList(1L, 2L)); // 根据多个 ID 查集合

Service 层实战

很多时候,单条的 insert 无法满足复杂的业务场景(比如注册时要判断存在则更新,不存在则插入)。这时候就需要 Service 层出马了。它继承自 IService<T>

  1. 接口与实现类定义
1
2
3
4
5
6
7
8
// 1. 接口继承 IService
public interface UserService extends IService<User> {
}

// 2. 实现类继承 ServiceImpl,并实现自己的接口
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
  1. Service 层的独特功能

Service 层不仅封装了 Mapper 的所有功能,还自带事务控制批量处理能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. 批量保存 (saveBatch) —— 极致的性能优化
List<User> userList = new ArrayList<>();
// ... 模拟添加1000个用户对象到集合中
userService.saveBatch(userList); // 底层分批次提交,性能远超写个 for 循环调用 mapper.insert

// 2. 保存或更新 (saveOrUpdate) —— 智能路由
User user = new User();
user.setId(1001L); // 如果传入了ID,且数据库中有该ID,则执行 Update
user.setUserName("更新名字");
// user.setId(null); // 如果ID为空,则直接执行 Insert
userService.saveOrUpdate(user);

// 3. 链式查询 (Chain Query) —— 代码更优雅
List<User> list = userService.query()
.eq("user_account", "admin")
.list();

​ 3.Service层(this.xxx)的用法

1
2
3
4
5
6
7
8
9
10
11
// 在 UserServiceImpl 类的方法中:

boolean updateResult = this.update()
.eq("user_account", "admin") // 锁定条件 (WHERE)
.set("user_password", "888888") // 修改内容 (SET)
.set("status", 1) // 修改内容 (SET)
.update(); // 执行

if(!updateResult){
throw new RuntimeException("更新失败");
}

(1)查询

日常开发中最常打交道的就是查数据,除了this.query()有很多直接的方法:

  • this.getById(id):最简单的按主键查单条。

  • this.getOne(wrapper, throwEx)(高频踩坑点!) 根据条件查单条。如果数据库里符合条件的有多条记录,默认会报错。你可以传第二个参数 falsethis.getOne(wrapper, false)),这样遇到多条记录时,它会乖乖返回第一条而不报错。

  • this.list() / this.list(wrapper):查全表,或者根据条件查出一个 List 集合。

  • this.listByIds(idList):极度常用!传入一个 ID 的 List,直接返回这些 ID 对应的实体集合(比如在购物车里根据选中的商品 ID 列表查详情)。

  • this.count(wrapper):统计行数。比如 this.count(new QueryWrapper<User>().eq("status", 1)),相当于 SELECT COUNT(*) ...

  • this.page(page, wrapper):无脑分页查询。

(2)删除

  • this.removeById(id):根据 ID 删单条。
  • this.removeByIds(idList):根据 ID 集合批量删(比如后台管理系统的“批量删除”按钮)。
  • this.remove(wrapper):根据条件删(比如清空某个用户的所有登录日志:this.remove(new LambdaQueryWrapper<Log>().eq(Log::getUserId, 1001)))。

(3)链式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 链式查询:查出一个 List
List<User> list = this.query()
.eq("status", 1)
.like("user_name", "张")
.list(); // 最后以 .list() 结尾

// 链式查询:查出一个单条对象
User user = this.query().eq("user_account", "admin").one();

// 链式查询:查总数
long count = this.query().eq("role", "VIP").count();

// 链式删除:一行代码搞定条件删除
this.update().eq("status", -1).remove();

条件构造器 (Wrapper)

如何不写 SQL 就能完成复杂的 WHERE 条件过滤?MP 提供了强大的 Wrapper 机制。在企业开发中,强烈推荐使用 LambdaQueryWrapper,它可以避免字段名拼写错误。

1
2
3
4
5
6
7
8
9
// 场景:在 API 开放平台中,查询账号为 admin,且状态为正常的接口调用者,按创建时间倒序排
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();

wrapper.eq(User::getUserAccount, "admin") // WHERE user_account = 'admin'
.eq(User::getStatus, 0) // AND status = 0
.like(User::getUserName, "测试") // AND user_name LIKE '%测试%'
.orderByDesc(User::getCreateTime); // ORDER BY create_time DESC

List<User> userList = userMapper.selectList(wrapper);

常用条件指令速查表:

  • eq (等于 =)、ne (不等于 <>)
  • gt (大于 >)、ge (大于等于 >=)
  • lt (小于 <)、le (小于等于 <=)
  • like (模糊查询 LIKE ‘%值%’)
  • in (包含 IN (v1, v2, …))

进阶实用功能

除了基础 CRUD,MP 还内置了非常多解决开发痛点的插件:

  1. 分页插件: 只需配置一个拦截器,调用 selectPage 方法,MP 会自动帮你执行 COUNT 查询并拼接 LIMIT 语句,从此告别手写分页逻辑。
  2. 自动填充: 通过 @TableField(fill = FieldFill.INSERT) 配合元对象处理器,可以在插入或更新时,自动填充 create_timeupdate_time,再也不用每次 set 时间了。
  3. 逻辑删除:is_deleted 字段加上 @TableLogic 注解。调用 deleteById 时,底层会自动变成 UPDATE is_deleted = 1,并在随后的所有 select 查询中,自动带上 WHERE is_deleted = 0 的过滤条件。

总结

什么时候用 Mapper?

只需要对单表进行最简单的数据库增删改查交互时。

什么时候用 Service?

需要进行批量操作(如 saveBatch)、包含判断逻辑的存储(如 saveOrUpdate)、或者涉及复杂业务流程和事务控制时。

什么时候必须手写 SQL?

当遇到多张表关联查询(JOIN),或者极其复杂的报表聚合统计时。MyBatis-Plus 提倡的原则是:“简单的单表交给框架,复杂的多表留给手写”

现在本人还处于入门阶段,如果后面有新的见解会继续记录下来,并且本章可能出现的错误或者不精炼的内容发现了也会及时更新修改。