需求是用 SpringBoot + MyBatis-Plus 读 user 表返回给前端,四层结构,五个接口,另外两个自定义查询。

建表

1
2
3
4
5
6
7
8
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(50) NOT NULL COMMENT '姓名',
`birthday` date DEFAULT NULL COMMENT '生日',
`sex` varchar(2) DEFAULT NULL COMMENT '性别',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

插两条测试数据:

1
2
3
INSERT INTO `user` (`name`, `birthday`, `sex`, `create_time`) VALUES
('张三', '2003-05-12', '男', '2026-10-20 10:20:00'),
('李四', '2004-11-03', '女', '2026-10-20 10:21:00');

新建项目配置

依赖

Spring Boot 4 要用 mybatis-plus-spring-boot4-starter,原来的 mybatis-plus-boot-starter 起不来,报自动配置的错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>

<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<version>3.5.17</version>
</dependency>

webmvc 是建项目自带的。jdbc 那个 MyBatis-Plus 自己会带,多一个不碍事。lombok 用来省 getter setter。

配置

1
2
3
4
5
6
7
8
9
10
11
12
spring.application.name=demo-l06wys
server.port=8080

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://你的地址:3306/你的库?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.username=你的账号
spring.datasource.password=你的密码

mybatis-plus.mapper-locations=classpath:mapper/*.xml
mybatis-plus.type-aliases-package=com.example.demol06wys.domain
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

mapper-locations 不写的话 xml 加载不到,调 xml 里的方法报 Invalid bound statement。

最后一行打印 SQL,调试的时候能看到实际执行的语句和参数。

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
@TableName("`user`")
public class User implements Serializable {

private static final long serialVersionUID = 1L;

@TableId(type = IdType.AUTO)
private Integer id;
private String name;
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate birthday;
private String sex;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
}

表名加反引号,user 跟 SQL 关键字容易撞。

@TableId 的 type 写 AUTO。不写默认走雪花算法,主键变成十九位数字,跟表里的自增对不上。

时间字段加 @JsonFormat,不加返回的是 2026-10-20T10:20:00,带个 T。

Mapper 和 xml

接口:

1
2
3
4
5
6
7
8
@Mapper
public interface UserMapper extends BaseMapper<User> {

@Select("select id, name, birthday, sex, create_time from `user` order by name desc")
List<User> getAllUserSortByNameDesc();

List<User> selectUserList(@Param("name") String name);
}

继承 BaseMapper,基础的增删改查不用自己写。

倒序那句只有一行 SQL,写在注解上。带条件的要拼 where,放 xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<mapper namespace="com.example.demol06wys.mapper.UserMapper">

<select id="selectUserList" resultType="com.example.demol06wys.domain.User">
select id, name, birthday, sex, create_time
from `user`
<where>
<if test="name != null and name != ''">
name like concat('%', #{name}, '%')
</if>
</where>
order by id
</select>

</mapper>

where 会自动去掉多余的 and,if 不成立整段不拼,name 不传就是查全部。

namespace 写全限定类名,id 跟方法名一样,这两处错了都是 Invalid bound statement。

Service

1
2
3
4
5
6
public interface UserService extends IService<User> {

List<User> getAllUserSortByNameDesc();

List<User> selectUserList(String name);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

@Override
public List<User> getAllUserSortByNameDesc() {
return baseMapper.getAllUserSortByNameDesc();
}

@Override
public List<User> selectUserList(String name) {
return baseMapper.selectUserList(name);
}
}

包名是 com.baomidou.mybatisplus.spring.service.IService,3.5.17 从 extension 拆出来了,照网上教程 import 会编译不过。实现类在同级 impl 下。

泛型先 Mapper 后实体,写反启动报错。

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@RestController
@RequestMapping("/api/user")
@CrossOrigin
public class UserController {

@Autowired
private UserService userService;

@GetMapping("/{id}")
public User getById(@PathVariable Integer id) {
return userService.getById(id);
}

@GetMapping("/list")
public List<User> list() {
return userService.list();
}

@PostMapping
public User save(@RequestBody User user) {
if (user.getCreateTime() == null) {
user.setCreateTime(LocalDateTime.now());
}
userService.save(user);
return user;
}

@PutMapping
public User update(@RequestBody User user) {
userService.updateById(user);
return userService.getById(user.getId());
}

@DeleteMapping("/{id}")
public boolean delete(@PathVariable Integer id) {
return userService.removeById(id);
}

@GetMapping("/sortByNameDesc")
public List<User> sortByNameDesc() {
return userService.getAllUserSortByNameDesc();
}

@GetMapping("/selectUserList")
public List<User> selectUserList(@RequestParam(required = false) String name) {
return userService.selectUserList(name);
}
}

新增前端不传创建时间,判空补一个当前时间。

updateById 只改传了值的字段,null 跳过,所以改一个字段不用先查整条。

@RequestParam(required = false) 让 name 可以不传。

@CrossOrigin 给前端调试用,不加会报跨域。

启动

1
2
3
4
5
6
7
8
@SpringBootApplication
@MapperScan("com.example.demol06wys.mapper")
public class DemoL06wysApplication {

public static void main(String[] args) {
SpringApplication.run(DemoL06wysApplication.class, args);
}
}

浏览器访问 http://localhost:8080/api/user/list,能出 JSON 就说明库连上了:

1
2
3
4
[
{"id":1,"name":"张三","birthday":"2003-05-12","sex":"男","createTime":"2026-10-20 10:20:00"},
{"id":2,"name":"李四","birthday":"2004-11-03","sex":"女","createTime":"2026-10-20 10:21:00"}
]

mvnw 启动报端口占用的话,换 IDEA 里的启动按钮就行。

测接口

ApiFox 建个项目,七个请求都测了。地址都是 http://localhost:8080/api/user。

查单个,GET /api/user/1:

1
{"id":1,"name":"张三","birthday":"2003-05-12","sex":"男","createTime":"2026-10-20 10:20:00"}

查全部,GET /api/user/list:

新增,POST /api/user,body 不传 id:

1
2
3
4
5
{
"name": "王五",
"birthday": "2005-01-01",
"sex": "男"
}

返回:

1
{"id":5,"name":"王五","birthday":"2005-01-01","sex":"男","createTime":"2026-10-24 01:20:31"}

id 和时间都自动补上了。返回的 id 要是长数字,检查 @TableId。

更新,PUT /api/user,body 带 id:

1
2
3
4
5
6
{
"id": 5,
"name": "王五改",
"birthday": "2005-02-02",
"sex": "女"
}

返回:

1
{"id":5,"name":"王五改","birthday":"2005-02-02","sex":"女","createTime":"2026-10-24 01:20:31"}

createTime 没变,因为这次没传,updateById 跳过 null。

删除,DELETE /api/user/5,返回 true,删完再查一次列表确认。

按姓名倒序,GET /api/user/sortByNameDesc:

1
2
3
4
[
{"id":2,"name":"李四","birthday":"2004-11-03","sex":"女","createTime":"2026-10-20 10:21:00"},
{"id":1,"name":"张三","birthday":"2003-05-12","sex":"男","createTime":"2026-10-20 10:20:00"}
]

条件查询,GET /api/user/selectUserList?name=张:

1
[{"id":1,"name":"张三","birthday":"2003-05-12","sex":"男","createTime":"2026-10-20 10:20:00"}]

name 留空再请求一次,返回全部,说明 if 生效了。

记一下坑

  • user 加反引号,@TableName 和 SQL 里都要
  • @TableId 不写 type = AUTO,主键变成雪花 ID
  • Invalid bound statement,先看 mapper-locations,再看 namespace 和 id
  • Spring Boot 4 用 boot4 的 starter,IService 包名在 spring 下面
  • 时间字段不加 @JsonFormat 会带个 T
  • 端口被占,换 IDEA 启动

广告
广告正在加载中...
暂不开放评论,如对本文有任何疑问或建议,请联系apple@mr-wu.top
All Tags