一、任务要求
- SpringBoot + MyBatis-Plus 读取数据库数据返回给前端,需要实现对应的controller、domain、mapper、service四层;
- 数据表
user结构:id(int 主键)、name(varchar 50)、birthday(date)、sex(varchar 1)、create_time(datetime); - 接口统一根地址
/api/user,实现 5 个接口:- 3.1 根据用户 id 获取用户信息
@GetMapping("/{id}") - 3.2 获取所有用户信息
@GetMapping("list") - 3.3 添加用户信息
@PostMapping - 3.4 更新某个用户信息(根据 id)
@PutMapping - 3.5 删除某个用户信息
@DeleteMapping("/{id}")
- 3.1 根据用户 id 获取用户信息
- Service、Mapper 增加自定义方法:
getAllUserSortByNameDesc:直接写 SQL(注解方式),按姓名降序;selectUserList:与 mapper.xml 配合使用;- controller 中编写调用的两个接口。
- 代码 + 博客上交,博客记录完整开发过程并附 Postman/ApiFox 测试运行结果。
二、环境准备与建表
2.1 环境版本
| 组件 | 版本 |
|---|---|
| JDK | 17 |
| Spring Boot | 4.1.1 |
| MyBatis-Plus | 3.5.16(mybatis-plus-spring-boot4-starter) |
| MySQL | 8.0 |
| 数据库 | mybatis,表user,预置 50 条测试数据 |
2.2 建表 SQL
CREATETABLE`user`(`id`INTNOTNULLAUTO_INCREMENTCOMMENT'主键',`name`VARCHAR(50)DEFAULTNULLCOMMENT'姓名',`birthday`DATEDEFAULTNULLCOMMENT'生日',`sex`VARCHAR(1)DEFAULTNULLCOMMENT'性别',`create_time`DATETIMEDEFAULTNULLCOMMENT'创建时间',PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;注意表名
user在 MySQL 里是保留字,SQL 里要写成反引号`user`。
三、创建工程与依赖
Spring Initializr 生成骨架后,pom.xml关键依赖:
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>4.1.1</version></parent><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webmvc</artifactId></dependency><!-- ✅ Boot 4.x 专用 MyBatis-Plus starter,不能混用 Boot2/3 的 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot4-starter</artifactId><version>3.5.16</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies>踩坑记录 ①:Spring Boot 4.x 必须用mybatis-plus-spring-boot4-starter,网上教程常见的mybatis-plus-boot-starter(Boot2/3 用)混用会启动报错。
数据源配置application.yml
server:port:8080spring:datasource:driver-class-name:com.mysql.cj.jdbc.Driverurl:jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername:rootpassword:1234mybatis-plus:mapper-locations:classpath*:/mapper/**/*.xmlconfiguration:log-impl:org.apache.ibatis.logging.stdout.StdOutImpl# 控制台打印 SQL,方便调试global-config:db-config:id-type:auto# 主键自增四、四层代码实现
项目结构:
src/main/java/com/example/l06mybatis/ ├── L06MybatisApplication.java ├── config/ │ └── MyMetaObjectHandler.java # 字段自动填充(后期优化加入) ├── controller/ │ └── UserController.java ├── domain/ │ └── User.java ├── mapper/ │ └── UserMapper.java ├── service/ │ ├── UserService.java │ └── impl/UserServiceImpl.java └── tools/ └── Result.java # 统一响应封装 src/main/resources/mapper/ └── UserMapper.xml4.1 domain 层 —— 实体类
@Data@TableName("`user`")publicclassUserimplementsSerializable{privatestaticfinallongserialVersionUID=1L;@TableId(type=IdType.AUTO)privateIntegerid;privateStringname;privateLocalDatebirthday;privateStringsex;@TableField(fill=FieldFill.INSERT)// 插入时由 MetaObjectHandler 自动填充privateLocalDateTimecreateTime;}实体字段与表字段一一对应(驼峰 ↔ 下划线由 MyBatis-Plus 自动映射);LocalDate对应date,LocalDateTime对应datetime。
4.2 mapper 层 —— BaseMapper + 注解 SQL
@MapperpublicinterfaceUserMapperextendsBaseMapper<User>{// 自定义方法1:直接写 SQL(注解方式),按姓名降序排序@Select("SELECT id, name, birthday, sex, create_time FROM `user` ORDER BY name DESC")List<User>getAllUserSortByNameDesc();// 自定义方法2:配合 UserMapper.xml 中 <select id="selectUserList">List<User>selectUserList();}继承BaseMapper<User>后,单表 CRUD(selectById、insert、updateById、deleteById)全部白拿,一行 SQL 不用写。
4.3 mapper.xml —— XML 方式查询
src/main/resources/mapper/UserMapper.xml:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.example.l06mybatis.mapper.UserMapper"><!-- 自定义方法2:与 UserMapper.selectUserList() 对应,查询全部用户 --><selectid="selectUserList"resultType="com.example.l06mybatis.domain.User">SELECT id, name, birthday, sex, create_time FROM `user`</select></mapper>namespace 必须是 Mapper 接口全限定名,id必须与方法名一致。
4.4 service 层
publicinterfaceUserServiceextendsIService<User>{UsergetUserById(Longid);booleancreateUser(Useruser);booleanupdateUserById(Useruser);booleanremoveUserById(Longid);// 自定义方法List<User>getAllUserSortByNameDesc();List<User>selectUserList();}@ServicepublicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsUserService{@OverridepublicUsergetUserById(Longid){returngetById(id);}@OverridepublicbooleancreateUser(Useruser){returnsave(user);}@OverridepublicbooleanupdateUserById(Useruser){returnupdateById(user);}@OverridepublicbooleanremoveUserById(Longid){returnremoveById(id);}@OverridepublicList<User>getAllUserSortByNameDesc(){returnbaseMapper.getAllUserSortByNameDesc();}@OverridepublicList<User>selectUserList(){returnbaseMapper.selectUserList();}}继承IService/ServiceImpl,CRUD 直接调用 MyBatis-Plus 提供的getById / save / updateById / removeById。
4.5 tools —— 统一响应封装
@DatapublicclassResult<T>{privateIntegercode;privateStringmessage;privateTdata;publicstatic<T>Result<T>success(Tdata){Result<T>result=newResult<>();result.code=200;result.message="success";result.data=data;returnresult;}publicstatic<T>Result<T>error(Integercode,Stringmessage){Result<T>result=newResult<>();result.code=code;result.message=message;returnresult;}}4.6 controller 层 —— 5 个作业接口 + 2 个自定义接口
@RestController@RequestMapping("/api/user")publicclassUserController{@AutowiredpublicUserServiceuserService;// 3.1 根据 id 查询单个用户(查不到时返回失败 Result)@GetMapping("/{id}")publicResult<User>getUser(@PathVariableLongid){Useruser=userService.getUserById(id);if(user==null){returnResult.error(404,"用户不存在: id="+id);}returnResult.success(user);}// 3.2 查询全部用户列表@GetMapping("list")publicResult<List<User>>getUserList(){returnResult.success(userService.list());}// 3.3 新增用户@PostMappingpublicResult<Boolean>createUser(@RequestBodyUseruser){returnResult.success(userService.createUser(user));}// 3.4 修改用户@PutMappingpublicResult<Boolean>updateUser(@RequestBodyUseruser){returnResult.success(userService.updateUserById(user));}// 3.5 删除用户@DeleteMapping("/{id}")publicResult<Boolean>deleteUser(@PathVariableLongid){returnResult.success(userService.removeUserById(id));}// 自定义接口1:按姓名降序排序查询(Mapper 注解 SQL)@GetMapping("sortByNameDesc")publicResult<List<User>>getUserSortByNameDesc(){returnResult.success(userService.getAllUserSortByNameDesc());}// 自定义接口2:配合 UserMapper.xml 查询用户列表@GetMapping("selectUserList")publicResult<List<User>>getSelectUserList(){returnResult.success(userService.selectUserList());}}4.7 字段自动填充(后期优化)
最初版本 POST 之后查询create_time是 null——实体插入时没赋值、表字段也没有默认值。用 MyBatis-Plus 的自动填充解决:
@ComponentpublicclassMyMetaObjectHandlerimplementsMetaObjectHandler{@OverridepublicvoidinsertFill(MetaObjectmetaObject){this.strictInsertFill(metaObject,"createTime",LocalDateTime.class,LocalDateTime.now());}@OverridepublicvoidupdateFill(MetaObjectmetaObject){}}踩坑记录 ②:strictInsertFill只对实体上标注了@TableField(fill = FieldFill.INSERT)的字段生效,两个要配套。
五、开发过程中排查的两个问题
新增/修改后
createTime返回 null
原因:实体插入时未给create_time赋值,数据库字段也没设默认值。
解决:实体加@TableField(fill = FieldFill.INSERT)+ 新增MyMetaObjectHandler,插入时自动写入当前时间。修复后回查"createTime":"2026-10-22T20:25:58"。按 id 查询不存在的记录返回
data:null,前端无法区分"查询成功但为空"和"用户不存在"
解决:Result增加error(code, message),Controller 判空后返回{"code":404,"message":"用户不存在: id=999"},语义更明确。
六、Postman 接口测试结果
测试方式:Postman 导入集合,对 5 个作业接口 + 2 个自定义接口逐个发起请求(新增→修改→删除使用临时数据并自清理)。
测试结果总览
| # | 接口 | 方法 | 请求路径 | HTTP | 业务 code | 结果 |
|---|---|---|---|---|---|---|
| 1 | 查询全部用户列表 | GET | /api/user/list | 200 | 200 | ✅ 50 条数据 |
| 2 | 按 id 查询单个用户 | GET | /api/user/1 | 200 | 200 | ✅ |
| 2b | 按 id 查询(不存在) | GET | /api/user/999 | 200 | 404 | ✅ 返回"用户不存在" |
| 3 | 新增用户 | POST | /api/user | 200 | 200 | ✅ 落库成功,createTime 自动填充 |
| 4 | 修改用户 | PUT | /api/user | 200 | 200 | ✅ 复查字段已更新 |
| 5 | 删除用户 | DELETE | /api/user/{id} | 200 | 200 | ✅ 复查已删除 |
| 6 | 自定义①按姓名降序 | GET | /api/user/sortByNameDesc | 200 | 200 | ✅ 注解 SQL 生效 |
| 7 | 自定义②mapper.xml 查询 | GET | /api/user/selectUserList | 200 | 200 | ✅ XML 配置生效 |
6.1 GET /api/user/list —— 查询全部用户![]()
返回 50 条用户数据,code=200,字段完整(id / name / birthday / sex / createTime)。
6.2 GET /api/user/1 —— 按 id 查询
返回 id=1 的用户"罗秀兰",与列表第 1 条一致。
6.3 POST /api/user —— 新增用户
请求体{"name":"张三","birthday":"1999-01-01","sex":"男"},响应data:true;回查列表确认落库(id 自增)。
6.4 PUT /api/user —— 修改用户
按 id 修改姓名/生日/性别,响应data:true,按 id 复查确认修改生效。
6.5 DELETE /api/user/{id} —— 删除用户
删除新增的临时用户,响应data:true;复查单查返回"用户不存在"、列表回到 50 条,测试数据自清理。
6.6 GET /api/user/sortByNameDesc —— 自定义①(注解 SQL)
Mapper 中直接写 SQLORDER BY name DESC,返回 50 条按姓名降序排列。
6.7 GET /api/user/selectUserList —— 自定义②(mapper.xml)
Mapper 接口方法与UserMapper.xml的<select>配合,查询结果与 list 接口一致,证明 XML 配置生效。
七、总结
- MyBatis-Plus 的价值:单表 CRUD 通过
BaseMapper+IService零 SQL 完成;需要自定义 SQL 时,注解@Select和 XML 两种方式都能与 Service 层无缝衔接,本实验两种方式各实现了一个。 - 四层架构:Controller 只做参数接收与结果包装,Service 承接业务,Mapper 负责数据访问,domain 承载表结构——层次清晰,换 ORM 或改业务互不影响。
- 踩坑收获:Boot 4.x 要用对应的
mybatis-plus-spring-boot4-starter;user是 MySQL 保留字要加反引号;字段自动填充需要@TableField(fill=...)与MetaObjectHandler配套;接口对"查不到"的场景应返回明确的业务错误码而不是null。 - 测试闭环:Postman 全部 7 个接口 200 通过,测试数据自清理,未污染原始数据。