MyBatis-Plus学习笔记
今天把 MyBatis-Plus 的基础链路梳了一遍
这篇算是我今天的学习记录,内容主要围绕 Spring Boot、MyBatis-Plus、MySQL 连接、分页、条件构造器这些基础知识。之前看这些概念的时候总觉得都是零散的,今天顺着“项目到底是怎么连上数据库并跑起来的”这条线重新过了一遍,脑子里清楚了不少。
先说结论:MyBatis-Plus 到底是什么
我现在对 MyBatis-Plus 的理解比较直接:
它不是用来替代 MyBatis 的,而是在 MyBatis 上加了一层常用能力,把很多重复工作提前帮你做好了。
比如以前做单表增删改查,可能要自己写 Mapper、写 XML、写 SQL。用了 MyBatis-Plus 以后,很多基础方法已经准备好了,像 insert、deleteById、selectById、updateById 这些不需要每次都重新写。
所以它最核心的价值不是“多高级”,而是两个字:省事。
尤其是在刚开始搭后台项目的时候,这种省事会很明显。
yml 和 properties 到底差在哪
这两个东西本质上都是 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 是否允许远程连接
也就是说,数据库装好只是前提,不等于已经打通了。
我现在会把“打通数据库”理解成一个完整链路:
- 虚拟机里 MySQL 已经装好并启动
- 对应数据库已经创建,比如
hello_mp - 账号密码是对的
- 主机能访问到虚拟机的 IP
- 3306 端口没有被拦住
- 项目本身也有正确的 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>,那很多基础方法其实已经有了,比如:
insertdeleteByIdupdateByIdselectByIdselectList
所以我现在会把 Mapper 理解成:
它不是业务逻辑层,而是专门负责“对哪张表做什么数据库操作”的那一层。
今天顺手也把几个常见注解过了一遍
@SpringBootTest
这个注解是用在测试类上的。
它的作用可以简单理解成:把整个 Spring Boot 环境启动起来再做测试。
这样测试类里就能直接拿到容器里的 Bean,比如 UserMapper、UserService、DataSource 这些。
@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 这种代理增强
简单说就是:
变量类型写接口,真正注入进来的通常是实现类对象,或者实现类对应的代理对象。
IService、ServiceImpl、BaseMapper 三者关系
这三个今天也是一开始有点混,后面理顺了。
我的理解是:
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。
这个设计还是挺方便的。
IPage 和 Page 不用再混了
今天顺手也把这两个类捋了一下。
IPage:分页结果接口Page:IPage的常用实现类
平时最常见的写法就是:
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 项目的时候。
目前我记住的几个常用点就够了:
- 可以在 Mapper 和 XML 之间快速跳转
- 可以根据表结构生成代码
- 对 MyBatis-Plus 项目识别也比较友好
如果是刚搭项目,拿它去生成 entity、mapper、service、xml 这些骨架文件,能省不少时间。
今天顺手记下的一个 IDEA 小技巧
比如写了这样一行:
new User();
如果想快速变成:
User user = new User();
可以用:
Alt + EnterCtrl + Alt + V
虽然是很小的细节,但写代码的时候确实挺顺手。
今天这波学习,最重要的是把链路看清了
回头看,今天真正让我有收获的不是死记了多少概念,而是把这条线慢慢串起来了:
- Spring Boot 配置文件怎么写
- 项目怎么连上虚拟机里的 MySQL
- Mapper、Service 各自负责什么
- MyBatis-Plus 为什么能少写这么多代码
- 条件构造器到底在干什么
- 分页插件到底是怎么生效的
之前这些点是散的,现在至少能放到同一张图里看了。
如果要给今天的内容做一个收尾,那我会这么记:
今天不是学了几个零碎知识点,而是把 MyBatis-Plus 最基础的一条开发链路从头到尾顺了一遍。
后面再往下学 CRUD、分页结果处理、逻辑删除、自动填充、事务这些内容,应该就没那么容易乱了。
浙公网安备 33010602011771号