MyBatis-Plus学习笔记

今天把 MyBatis-Plus 的基础链路梳了一遍

这篇算是我今天的学习记录,内容主要围绕 Spring Boot、MyBatis-Plus、MySQL 连接、分页、条件构造器这些基础知识。之前看这些概念的时候总觉得都是零散的,今天顺着“项目到底是怎么连上数据库并跑起来的”这条线重新过了一遍,脑子里清楚了不少。


先说结论:MyBatis-Plus 到底是什么

我现在对 MyBatis-Plus 的理解比较直接:

它不是用来替代 MyBatis 的,而是在 MyBatis 上加了一层常用能力,把很多重复工作提前帮你做好了。

比如以前做单表增删改查,可能要自己写 Mapper、写 XML、写 SQL。用了 MyBatis-Plus 以后,很多基础方法已经准备好了,像 insertdeleteByIdselectByIdupdateById 这些不需要每次都重新写。

所以它最核心的价值不是“多高级”,而是两个字:省事

尤其是在刚开始搭后台项目的时候,这种省事会很明显。


ymlproperties 到底差在哪

这两个东西本质上都是 Spring Boot 的配置文件,能干的事情差不多。

区别主要还是写法。

properties 是这种扁平的:

server.port=8080
spring.datasource.username=root
spring.datasource.password=123456

yml 是这种层级结构:

server:
  port: 8080

spring:
  datasource:
    username: root
    password: 123456

我自己的感觉是:

  • 配置少的时候,properties 很直接
  • 配置一多,尤其是有一层一层嵌套的时候,yml 看起来舒服很多

当然 yml 也有一个很现实的问题,就是它对缩进比较敏感,空格多一个少一个都可能出事。所以写的时候不能太随意。


代码怎么连上虚拟机里的 MySQL

这个问题我今天算是彻底理顺了。

之前总觉得“我把 MySQL 装好,把数据库建出来,项目应该就能连上了吧”。实际上没有这么简单。

Spring Boot 里数据库连接一般会写在 application.yml 里,例如:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.10.101:3306/hello_mp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: mpuser
    password: 123456

这几项里真正最关键的是下面几个点:

  • IP 对不对
  • 端口能不能通
  • 数据库名存不存在
  • 用户名和密码对不对
  • MySQL 是否允许远程连接

也就是说,数据库装好只是前提,不等于已经打通了

我现在会把“打通数据库”理解成一个完整链路:

  1. 虚拟机里 MySQL 已经装好并启动
  2. 对应数据库已经创建,比如 hello_mp
  3. 账号密码是对的
  4. 主机能访问到虚拟机的 IP
  5. 3306 端口没有被拦住
  6. 项目本身也有正确的 MySQL 驱动和配置

只要这几个环节有一个不通,项目就连不上。

顺便也记了一下 useUnicode=true 这种参数的作用。它主要是为了避免中文乱码,属于编码相关配置,不是决定数据库能不能连接上的关键。


我对项目分层的理解又清楚了一点

今天学 MyBatis-Plus 的时候,顺手把常见分层也一起重新理解了一遍。

项目里很常见的一条调用链是:

Controller -> Service -> Mapper -> MySQL

我现在对这几个层的理解是:

  • Controller:接请求,回结果
  • Service:写业务逻辑
  • Mapper:真正和数据库打交道

以前我容易把 Service 和 Mapper 混在一起,觉得都是“查数据库”。现在能分开看了:

Mapper 更像工具层,Service 更像业务层。


Mapper 到底是干什么的

Mapper 本质上就是访问数据库的接口。

比如:

public interface UserMapper extends BaseMapper<User> {
}

如果它继承了 BaseMapper<User>,那很多基础方法其实已经有了,比如:

  • insert
  • deleteById
  • updateById
  • selectById
  • selectList

所以我现在会把 Mapper 理解成:

它不是业务逻辑层,而是专门负责“对哪张表做什么数据库操作”的那一层。


今天顺手也把几个常见注解过了一遍

@SpringBootTest

这个注解是用在测试类上的。

它的作用可以简单理解成:把整个 Spring Boot 环境启动起来再做测试

这样测试类里就能直接拿到容器里的 Bean,比如 UserMapperUserServiceDataSource 这些。


@Autowired

这个就比较常见了,作用是让 Spring 自动把对象注入进来。

例如:

@Autowired
private UserMapper userMapper;

意思就是:不用自己 new,让 Spring 帮忙把 UserMapper 准备好。


@Test

这个注解的作用比较直接,就是告诉测试框架:

这是一个测试方法,运行测试时要执行它。

没有它的话,那个方法就只是普通方法。


@Configuration@Bean

这两个一般会搭配着出现。

@Configuration 表示当前类是一个配置类。

@Bean 表示把方法返回的对象交给 Spring 容器管理。

比如配置 MyBatis-Plus 分页插件时,会写类似这样的代码:

@Configuration
public class MPConfiguration {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

我现在会把这段代码翻译成一句人话:

向 Spring 容器里注册一个 MyBatis-Plus 的拦截器,并告诉它当前数据库是 MySQL。


UserServiceImpl 这类写法,今天终于没那么绕了

像下面这种代码,刚开始看很容易觉得一长串特别吓人:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

现在拆开看其实就没那么复杂了。

  • @Service:这是业务层组件,交给 Spring 管
  • implements UserService:说明它是 UserService 接口的实现类
  • extends ServiceImpl<UserMapper, User>:继承了 MyBatis-Plus 已经写好的通用 Service 实现

也就是说,这个类虽然看起来是空的,但其实已经拿到了很多基础 CRUD 能力。

这也是 MyBatis-Plus 比较方便的地方:很多基础代码框架都替你写好了。


为什么注入的是 UserService,不是 UserServiceImpl

这个点我今天也算真的理解了。

比如常见写法是:

@Autowired
private UserService userService;

而不是:

@Autowired
private UserServiceImpl userService;

原因其实就是 Java 里很经典的那句话:

面向接口编程,不要直接面向实现。

这样写的好处是:

  • 耦合度更低
  • 以后换实现类更方便
  • 更符合 Spring 的设计思路
  • 也更适合事务、AOP 这种代理增强

简单说就是:

变量类型写接口,真正注入进来的通常是实现类对象,或者实现类对应的代理对象。


IServiceServiceImplBaseMapper 三者关系

这三个今天也是一开始有点混,后面理顺了。

我的理解是:

  • BaseMapper<T>:Mapper 层通用接口,直接操作数据库
  • IService<T>:Service 层通用接口,定义业务层的一套通用方法
  • ServiceImpl<M, T>IService 的默认实现,底层会去调用 Mapper

如果用一条线串起来,大概就是:

UserMapper extends BaseMapper<User>
UserService extends IService<User>
UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService

所以可以把它理解成:

Mapper 层有一套通用能力,Service 层也有一套通用能力,而 Service 的底层最终还是会调到 Mapper。


条件构造器,终于不再只是“会写不会讲”了

MyBatis-Plus 里一个很常用的东西就是条件构造器。

说白了,它就是:

用 Java 代码拼查询条件或者更新条件,而不是直接手写 SQL 字符串。

比如查询时很常见:

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1)
       .like(User::getName, "Tom");

更新时则会多一个 set

LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId, 1)
       .set(User::getName, "Tom")
       .set(User::getAge, 20);

我现在对它俩的区别记得比较清楚:

  • QueryWrapper:重点是查谁
  • UpdateWrapper:重点是改谁,以及改成什么

所以更新构造器比查询构造器多了一个很关键的能力,就是 set()


Lambda 版为什么更推荐

这个问题今天也顺着搞明白了。

普通写法一般是这种:

QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "Tom");

Lambda 写法是这种:

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "Tom");

功能上差不多,但 Lambda 写法更安全。

因为字符串字段名写错了,代码可能都能编译通过,问题要到运行时才发现;但 Lambda 写法是基于实体类 getter 方法的,重构的时候 IDEA 也更容易帮你一起改掉。

所以我现在的习惯会是:

能用 LambdaWrapper 就尽量用 LambdaWrapper。


User::getName 为什么最后能对应到数据库里的 name

这个点我之前一直以为是“框架自动猜到了”。

现在更准确的理解是:它其实经历了两步。

第一步,MyBatis-Plus 会先把 User::getName 解析成实体类属性 name

第二步,再根据实体类和表字段之间的映射关系,把属性名映射成真正的数据库列名。

所以本质上是:

User::getName -> 属性名 name -> 数据库字段 name

如果是 createTime 这种字段,通常又会走驼峰转下划线,最后对应成 create_time

这样一来,Lambda 写法既能保持代码可读性,也能减少字段名写错的风险。


我对“拦截器”和“分页为什么靠拦截器实现”也清楚了

拦截器这个词以前听过很多次,但没有特别具体的感觉。

今天我给自己的理解是:

拦截器就是在原本要执行的动作之前,先接管一下,做一些额外处理,再决定怎么往下执行。

在 MyBatis-Plus 的分页场景里,这个“额外处理”主要就是改 SQL。

比如原 SQL 可能只是:

select * from user

如果做分页,框架就要在执行前把它改成类似:

select * from user limit 0,10

很多时候还要顺便再生成一条统计总数的 SQL:

select count(*) from user

也就是说,分页这件事本质上不是“Java 里把结果截一截”这么简单,而是要在 SQL 层面做改写

这就是为什么分页功能一般会通过拦截器实现。


MyBatis-Plus 的分页方法今天也顺下来了

Mapper 层常见的是:

IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper);

Service 层常见的是:

IPage<T> page(IPage<T> page);
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);

这几个方法我现在的理解是:

  • selectPage:更偏 Mapper 层直接用
  • page:更偏 Service 层封装后用

例如:

Page<User> page = new Page<>(1, 10);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1);

IPage<User> result = userMapper.selectPage(page, wrapper);

或者:

Page<User> page = new Page<>(1, 10);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, "张");

IPage<User> result = userService.page(page, wrapper);

另外一个让我印象很深的点是:

就算是自己写 XML,也一样可以分页。

只要方法参数里带了 IPage,分页插件就会帮忙处理,XML 里不用自己去手动写 limit

这个设计还是挺方便的。


IPagePage 不用再混了

今天顺手也把这两个类捋了一下。

  • IPage:分页结果接口
  • PageIPage 的常用实现类

平时最常见的写法就是:

Page<User> page = new Page<>(1, 10);

它里面一般会带这些信息:

  • records:当前页数据
  • total:总条数
  • current:当前页
  • size:每页条数

所以以后看到分页返回值时,至少不会再一脸懵。


为什么一条 SQL 能插入多条数据

这个问题今天也顺便想明白了。

以前容易下意识觉得“一条 SQL 对应一条数据”,其实不是。

像下面这种写法,本来就是合法的:

INSERT INTO user(name, age)
VALUES ('Tom',18), ('Jack',20), ('Lucy',22);

数据库会把它理解成:

向同一张表一次性插入多组值。

这么做的好处也很明显:

  • 减少网络往返
  • 减少 SQL 解析次数
  • 批量写入效率更高

所以一条 SQL 插多条,并不是什么特殊技巧,而是 SQL 本身就支持这种写法。


MyBatisX 插件今天也简单看了一下

这个插件在 IDEA 里挺实用,特别是刚开始写 MyBatis 或 MyBatis-Plus 项目的时候。

目前我记住的几个常用点就够了:

  1. 可以在 Mapper 和 XML 之间快速跳转
  2. 可以根据表结构生成代码
  3. 对 MyBatis-Plus 项目识别也比较友好

如果是刚搭项目,拿它去生成 entitymapperservicexml 这些骨架文件,能省不少时间。


今天顺手记下的一个 IDEA 小技巧

比如写了这样一行:

new User();

如果想快速变成:

User user = new User();

可以用:

  • Alt + Enter
  • Ctrl + Alt + V

虽然是很小的细节,但写代码的时候确实挺顺手。


今天这波学习,最重要的是把链路看清了

回头看,今天真正让我有收获的不是死记了多少概念,而是把这条线慢慢串起来了:

  • Spring Boot 配置文件怎么写
  • 项目怎么连上虚拟机里的 MySQL
  • Mapper、Service 各自负责什么
  • MyBatis-Plus 为什么能少写这么多代码
  • 条件构造器到底在干什么
  • 分页插件到底是怎么生效的

之前这些点是散的,现在至少能放到同一张图里看了。

如果要给今天的内容做一个收尾,那我会这么记:

今天不是学了几个零碎知识点,而是把 MyBatis-Plus 最基础的一条开发链路从头到尾顺了一遍。

后面再往下学 CRUD、分页结果处理、逻辑删除、自动填充、事务这些内容,应该就没那么容易乱了。

posted @ 2026-04-19 19:58  柳成荫y  阅读(45)  评论(0)    收藏  举报