一、回顾JDBC 操作的问题与流程
- 问题:JDBC 是操作 MySQL 的传统方式,但步骤繁琐。
- 核心流程(共 9 步):
- 创建数据库连接池(DataSource)
- 获取数据库连接(Connection)
- 编写带占位符的 SQL
- 创建操作命令对象(Statement)
- 替换 SQL 占位符
- 执行 SQL
- 处理执行结果(查询返回 ResultSet,更新返回数量)
- 处理结果集
- 释放资源
二、MyBatis 的定义与背景
- 定位:优秀的持久层框架,用于简化 JDBC 开发,专注于程序与数据库的交互。
- 持久层的含义:对应 Web 分层中的 Dao 层,负责数据库操作。
MyBatis 作为框架仅简化操作逻辑,实际数据存储仍依赖 MySQL,因此必须引入MySQL 驱动
三、MyBatis⼊⻔
MyBatis 结合 Spring Boot 的使用流程分为 4 步:
- 准备工作:创建 Spring Boot 工程、准备数据库表、定义实体类;
- 配置依赖:引入 MyBatis 框架、MySQL 驱动等依赖,并配置数据库连接信息;
- 编写 SQL:通过注解或 XML 文件编写 SQL 语句;
- 测试:验证数据库操作效果。
3.1准备⼯作
3.1.1创建⼯程
创建springboot⼯程,并导⼊mybatis的起步依赖、mysql的驱动包
项目创建后 pom.xml 会自动导入 MyBatis、MySQL 驱动依赖,版本随 SpringBoot 适配(SpringBoot 3.X → MyBatis 3.X),参考:https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
3.1.2数据准备
创建⽤户表,并创建对应的实体类User
创建对应的实体类UserInfo(实体类的属性名与表中的字段名⼀ 一对应)
3.1.3配置数据库连接字符串
3.1.4写持久层代码
在项⽬中,创建持久层接⼝UserInfoMapper
Mybatis的持久层接规范⼀般都叫XxxMapper
- Mapper 接口的本质:MyBatis 的 “数据访问接口”,用于告诉 MyBatis “要执行什么 SQL” 的入口,MyBatis 会在运行时自动为这个接口生成实现类(代理对象),无需你手动写实现类;
- @Mapper 注解的作用:告诉 Spring Boot “这是 MyBatis 的 Mapper 接口”,Spring 会把这个接口的代理对象纳入容器管理,后续可以通过
@Autowired注入使用; - @Select 注解的作用:把 SQL 语句 “绑定” 到接口方法上,当你调用
queryAllUser()方法时,MyBatis 会执行注解里的 SQL,并且自动把查询结果(ResultSet)转换成List<UserInfo>(实体类集合)。
问题讲解:MyBatis 的@Mapper和 Spring 的@Service在 “交给 Spring 管理” 的逻辑上有本质区别!!
1.@Mapper不是 “直接交给 Spring 管理”
@Mapper是MyBatis 的注解,不是 Spring 的注解!它的作用是告诉 MyBatis:“这是一个 Mapper 接口,需要为它生成代理实现类”,而非直接让 Spring 接管。
而@Service是Spring 的注解,作用是标记普通类为 Spring Bean,Spring 会直接实例化这个类并放入容器。
两者的 “交给 Spring 管理” 路径完全不同:
| 注解 | 归属框架 | 核心作用 | Spring 管理的对象来源 |
|---|---|---|---|
@Service | Spring | 标记普通类为业务 Bean | Spring 直接实例化@Service标注的类本身 |
@Mapper | MyBatis | 标记接口为 MyBatis 映射接口 | MyBatis 生成接口的代理类,再交给 Spring 管理 |
2.为什么@Mapper不需要 “手动定义实现类 / 变量名”?
你看到的UserInfoMapper userInfoMapper是接口,不是普通类 —— 接口本身不能实例化,但 MyBatis 做了 “兜底”:
步骤 1:@Mapper触发 MyBatis 生成代理类
当你在接口上加@Mapper后,MyBatis-Spring 会通过动态代理为UserInfoMapper生成一个 “隐形的实现类”(比如叫UserInfoMapperProxy),这个代理类会:
- 实现
UserInfoMapper接口; - 重写
queryAllUser()方法,执行@Select里的 SQL; - 处理数据库连接、SQL 执行、结果映射等逻辑。
步骤 2:MyBatis 把代理类交给 Spring
MyBatis-Spring 会将生成的代理类注册为 Spring Bean(Bean 的类型是UserInfoMapper接口类型),放入 Spring 容器。
步骤 3:@Autowired直接注入代理对象
你在测试类中写:
spring 会从容器中找到 “UserInfoMapper类型的代理 Bean”,直接赋值给这个变量 ——你不需要手动写实现类,也不需要手动 new 对象,MyBatis+Spring 帮你全做了。
3.对比@Service:为什么觉得 “需要写成员变量”?
比如你写一个UserService:
你觉得 “需要写成员变量”,是因为:
@Service标注的是普通类,Spring 会实例化这个类,但类内部要用到其他 Bean(比如UserInfoMapper),必须手动注入(写成员变量 +@Autowired);- 而
@Mapper标注的是接口,你不会在接口里写成员变量,而是在 “使用这个接口的地方”(比如UserService、测试类)注入接口对象。
举个更直观的对比
| 场景 | @Service(普通类) | @Mapper(MyBatis 接口) |
|---|---|---|
| 定义方式 | 写完整的类,包含方法实现 | 只写接口,方法上标注 SQL(无实现) |
| Spring 管理的对象 | 类的实例对象 | MyBatis 生成的接口代理对象 |
| 使用时的注入方式 | 定义XXXService变量 +@Autowired | 定义XXXMapper变量 +@Autowired |
| 要不要写实现 | 必须自己写方法实现 | 不用写,MyBatis 根据 SQL 自动生成实现 |
关键总结
@Mapper≠@Service:前者是 MyBatis 的 “接口代理生成器”,后者是 Spring 的 “普通类 Bean 标记器”;- 不用写实现类:MyBatis 动态生成
UserInfoMapper的代理实现类,Spring 管理这个代理对象; - 变量名不是 “省了”:你在使用
UserInfoMapper的地方(测试类 / Service),依然需要定义UserInfoMapper类型的成员变量,再通过@Autowired注入 —— 只是不用自己写实现类而已。
对比熟悉的@Service更易理解
| 特性 | @Mapper+ MyBatis | @Service |
|---|---|---|
| 谁生成类 | MyBatis 运行时动态生成代理类 | 你自己手写类 |
| 谁注册为 Spring Bean | MyBatis-Spring 帮你注册 | Spring 自动扫描@Service注册 |
| 谁创建对象 | Spring 实例化代理类 | Spring 实例化你手写的类 |
| 你需要写的代码 | 只写接口 + SQL 注解 | 写完整类 + 方法实现 |
四、单元测试
在创建出来的SpringBoot⼯程中,在src下的test⽬录下,已经⾃动帮我们创建好了测试类,我们可以直接使⽤这个测试类来进⾏测试.
单元测试的核心作用
单元测试是在开发阶段验证 “某一个组件(这里是UserInfoMapper)是否能正常工作”,避免把问题带到线上 —— 这里的测试目标是:验证queryAllUser()方法能否成功从数据库查询到用户数据。
运行结果如下:
使⽤Idea⾃动⽣成测试类
在需要测试的Mapper接⼝中,右键->Generate->Test
记得加@SpringBootTest 注解,加载Spring运⾏环境(别忘记哟!)
关于测试的问题:
| 问题 | 答案 |
|---|---|
Test 目录下:能否自定义测试方法 | 可以,新增方法加@Test即可 |
| 执行方法需要点类的箭头吗 | 不用,点单个方法的箭头只跑当前方法,效率更高 |
启动测试后后续更改测试里的代码需要clean吗 | 不需要,仅改代码 / 依赖后出问题时才需要 |
| 要手动启动 SpringBoot 启动类吗 | 不需要,@SpringBootTest会自动启动 Spring 容器 |
额外小技巧
如果测试方法执行慢(因为@SpringBootTest会启动完整容器),可以针对 Mapper 测试用@MyBatisTest(轻量化,只启动 MyBatis 相关环境)适合纯 Mapper 层的测试。
验证数据库查询功能是否正常,把数据库中UserInfo表的所有数据查询出来并打印到控制台: