Java 开发——(下篇)从零开始搭建后端基础项目 Spring Boot 3 + MybatisPlus
上篇速递 - Spring Boot 3 + MybatisPlus
五、静态资源访问
1. 基础配置
在 Spring Boot 中访问静态资源非常方便。Spring Boot 默认支持从以下位置加载静态资源:
/META-INF/resources/
/resources/
/static/
/public/
这些目录下的文件可以直接通过 URL 访问。
例如,如果有一个图片文件放在 src/main/resources/static/logo.png
,那么可以通过 http://localhost:8080/logo.png
来访问这个图片。(假设服务器运行在 8080 端口)
定义过滤规则
在默认的情况下,访问静态资源的 URL
为 http://localhost:8080/图片名.文件后缀
如果想要自定义其他的过滤规则,可以在 application.properties
配置文件中进行配置。
例如,在 application.properties
中添加如下配置:
# 定义过滤规则 /images/**
spring.mvc.static-path-pattern=/images/**
这会告诉 Spring Boot 访问静态资源的 URL
为 http://localhost:8080/images/图片名.文件后缀
配置静态资源路径
如果想自定义静态资源的位置,或添加额外的静态资源目录,可以在 application.properties
配置文件中进行配置。
例如,在 application.properties
中添加如下配置:
# 定义多个位置,使用逗号分隔
spring.resources.static-locations=classpath:/my-resources/,classpath:/static/
这会告诉 Spring Boot 从 /my-resources/
和 /static/
这两个目录下加载静态资源。
2. 项目搭建
我们可以在项目中进行如下配置:
# 静态资源配置
# 定义过滤规则 /images/**
spring.mvc.static-path-pattern=/images/**# 定义静态资源位置,对静态资源进行放行允许浏览器访问 classpath:/static/
#spring.web.resources.static-locations=classpath:/static/# 定义静态资源位置,会覆盖原先默认的静态资源位置
# 定义多个位置,使用逗号分隔
# 用户上传文件位置 upload,相对于配置文件的位置,"/"代表服务器所在路径
spring.web.resources.static-locations=/upload/,classpath:/static/
3. 注意事项
当我们想要测试这个静态资源的访问时,可能会遇到以下这个问题:
我们使用 IDEA 开发,就会直接把电脑上的测试图片直接复制到项目的 /resources/static
目录下,然后用 http://localhost:8080/images/test.jpg
进行测试,发现测试图片加载失败了。
这并非项目配置的问题。
而是由于导入的图片是放到项目的 static 目录下,而编译后的 target 中没有该资源,所以访问不到。
简单来说就是项目需要重新编译 Rebuild Project
,重新编译后静态资源会被加载到 target
目录下,此时就可以通过浏览器访问到图片了。
六、文件上传
前端通过 API 接口上传文件是一个常见的需求,通常涉及以下几个步骤:前端准备文件数据、发送 HTTP 请求到后端、后端接收并处理文件。
在后端 Spring Boot 项目中,我们可以对接收的文件进行如下处理。
1. 配置文件上传
在 application.properties
中配置文件上传的最大大小:
# tomcat 限制请求上传文件的大小
# 每个文件配置最大大小,单个上传文件最大为 10MB
spring.servlet.multipart.max-file-size=10MB# 单次请求的文件总大小
#spring.servlet.multipart.max-request-size=10MB
2. 创建控制器
创建一个控制器 FileUploadController
来处理文件上传请求:
@RestController
public class FileUploadController {@PostMapping("/upload")public String upload(String nickname, MultipartFile photo, HttpServletRequest request) throws IOException {System.out.println(nickname);//获取图片原始名称System.out.println(photo.getOriginalFilename());// 获取文件类型System.out.println(photo.getContentType());// 设置上传图片的保存位置// 这表示上传文件的位置是动态的,是 tomcat 服务器所在位置// 获取服务器上某个虚拟路径的实际物理路径String path = request.getServletContext().getRealPath("/upload/");// 打印出图片上传位置System.out.println(path);// 保存图片saveFile(photo, path);return "上传成功";}private void saveFile(MultipartFile photo, String path) throws IOException {// 判断存储目录是否存在,如果不存在则创建该目录File dir = new File(path);if (!dir.exists()) {// 创建目录dir.mkdir();}// 储存后文件File file = new File(path + photo.getOriginalFilename());// 把网络上的文件存储到服务器本地photo.transferTo(file);}}
3. 设置静态资源放行
通过 request.getServletContext().getRealPath()
可以获取服务器上某个虚拟路径的实际物理路径,及 tomcat 服务器所在位置。
由上面的文件上传控制器所知,前端上传的图片被存放到了 tomcat 服务器所在位置的 upload
文件夹下。
如果我们想在前端(浏览器)访问到这些图片,可以把这个文件夹设置成静态资源目录。
在配置文件 application.properties
中如何获取到这个目录?
在配置文件中 “/” 代表服务器所在路径,故这个目录用 “/upload/” 表示。
# 定义静态资源位置,会覆盖原先默认的静态资源位置
# 定义多个位置,使用逗号分隔
# 用户上传文件位置 upload,相对于配置文件的位置,"/"代表服务器所在路径
spring.web.resources.static-locations=/upload/,classpath:/static/
需要注意的点:在开发阶段,Spring Boot 项目的内置 tomcat 服务器所在位置不是固定的!每次重启项目位置都会发生变化。
4. 测试方法
使用 Postman 测试下这个接口,文件上传的格式一般选择 form-data
格式。
在浏览器中访问:
http://localhost:8080/images/bus.jpg
七、拦截器
在 Spring Boot 中,拦截器(Interceptor)是一种常用的机制,用于在请求到达控制器之前或之后执行某些操作。拦截器可以用于日志记录、权限校验、性能监控等多种场景。
下面是如何在 Spring Boot 中定义和使用拦截器的详细步骤。
1. 定义拦截器
创建拦截器类:实现 HandlerInterceptor
接口或继承 HandlerInterceptorAdapter
类。
如,在项目中我们创建了一个拦截器类 LoginInterceptor
,让它继承 HandlerInterceptor
接口。
返回值 return
表示是否拦截:
true
:不拦截;false
:拦截。
// 添加拦截器
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 拦截器打印的信息,在控制台看到它就说明拦截器生效了System.out.println("拦截器打印的信息");return true;// 如果 false 代表拦截,具体的拦截规则在拦截器注册文件中}
}// config 中注册拦截器,使拦截器生效
2. 注册拦截器
我们需要在配置类中注册拦截器,拦截器才会生效。
注册拦截器:创建一个配置类,实现 WebMvcConfigurer
接口,并重写 addInterceptors
方法。
如,在项目中我们创建了配置类 WebConfig
,让它继承 WebMvcConfigurer
接口,重写 addInterceptors
方法,同时添加注解 @Configuration
使配置类生效。
在 addInterceptors
方法中,可以设置拦截的具体位置。
// 添加注解 @Configuration 使配置类生效
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// config 中注册拦截器,使拦截器生效// 表示拦截所有的请求
// registry.addInterceptor(new LoginInterceptor());// 有针对性的拦截// /hello 表示路由映射的路径 http://localhost:8080/hello?nickname=zhangsan&phone=123// 拦截所有/hello/开头的请求registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/hello/**");}
}
测试一个 /hello/
开头的请求,观察后端控制台中拦截器打印的信息。
八、RESTful 风格
RESTful(Representational State Transfer)风格是一种设计网络应用程序架构的方式,它利用 HTTP 协议的特性来实现客户端和服务器之间的通信。
RESTful API 设计的核心原则是使用标准的 HTTP 方法(如 GET、POST、PUT、DELETE 等)来操作资源,每个资源都有唯一的标识符(通常是 URL)。
下面是一些关于如何设计 RESTful API 的基本指南。
1. RESTful API 设计原则
资源命名
- 使用名词而不是动词来表示资源。
- 使用复数形式来表示集合资源。
- 使用连字符或驼峰命名法来提高可读性。
HTTP 方法(关键点)
GET
:用于获取资源。POST
:用于创建资源。PUT
:用于更新资源(替换整个资源)。PATCH
:用于部分更新资源。DELETE
:用于删除资源。
状态码
200 OK
:请求成功。201 Created
:资源已创建。204 No Content
:请求成功,但没有返回内容。400 Bad Request
:客户端请求有误。401 Unauthorized
:请求未授权。403 Forbidden
:请求被拒绝。404 Not Found
:资源未找到。500 Internal Server Error
:服务器内部错误。
版本控制
- 可以通过 URL 路径或请求头来实现API版本控制。
2. 在项目中应用
实体类
需要用到我们之前创建的实体类 User
// 用于封装参数的实体类
public class User {private String username;private String password;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "User{" +"username='" + username + '\'' +", password='" + password + '\'' +'}';}
}
控制类
创建一个控制器类 UserController
来尝试使用这个 RESTful 风格。
@RestController
public class UserController {// Get:用于获取资源// 动态方式获取 {id},需要添加注解 @PathVariable@GetMapping("/user/{id}")public String getUserById(@PathVariable int id) {return "根据 ID 获取用户信息:" + id;}// Post:用于创建资源@PostMapping("/user")public String addUser(User user) {System.out.println(user.toString());return "添加用户";}// Put:用于更新资源@PutMapping("/user")public String updateUser(User user) {return "更新用户";}// Delete:用于删除资源@DeleteMapping("/user/{id}")public String deleteUser(@PathVariable int id) {return "根据 ID 删除用户信息:" + id;}}
测试方法
比如测试一下第一个 GET 方法。
九、Swagger3
在 Spring Boot 3.0 中使用 Swagger 所需要的依赖和 2.7 有所不同。
原因很简单,Spring Boot 3.0 是一次大版本升级,故造成了很多不兼容更新。
例如最低兼容的 Java 版本为 17,底层也切换到了 Spring 6。
所以自 2020 年以来没有更新过的 springfox-swagger2 和 springfox-swagger-ui 自然不会兼容 2022 年发布的 Spring Boot 3.0。
1. 添加 Maven 依赖
在 pom.xml
文件中添加 Maven 依赖,用于 Swagger3 的使用。
<!-- Swagger(openAPI) 相关功能依赖-->
<dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.6.0</version>
</dependency>
<!-- 搭配校验使用,使用与 Spring Boot 相同的版本号 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId><version>3.3.4</version>
</dependency>
2. 配置 Swagger3
在 application.properties
中添加如下配置,启用并配置 OpenAPI
和 Swagger UI
。
OpenAPI
和 Swagger UI
有自己默认的访问地址,当然也可以自己设置。
# 配置 swagger
# 启用并配置 OpenAPI 文档生成
springdoc.api-docs.enabled=true
# 默认地址 http://localhost:8080/v3/api-docs
# 自定义访问地址
#springdoc.api-docs.path=/user-service/v3/api-docs# 配置 Swagger UI
springdoc.swagger-ui.enabled=true
# 默认地址 http://localhost:8080/swagger-ui/index.html
# 自定义访问地址
#springdoc.swagger-ui.path=/user-service/swagger-ui/index.html
然后,创建 Swagger3 的配置类 SwaggerConfig
,对文档的一些细节进行设置。
- title - 标题
- description - 描述
- version - 版本
@Configuration
public class SwaggerConfig {@Beanpublic OpenAPI swaggerOpenApi() {return new OpenAPI().info(new Info().title("XXX微服务平台").description("描述平台").version("v1.0.0")).externalDocs(new ExternalDocumentation().description("设计文档").url("https://blog.csdn.net/Sareur_1879?type=blog"));}
}
3. 网址访问
然后启动项目,就可以通过网址进行访问 API
文档了。
// 默认地址 http://localhost:8080/swagger-ui/index.html
// 自定义访问地址 http://localhost:8080/user-service/swagger-ui/index.html
4. 存在的问题
在 Swagger2 版本是可以正常上传文件的(我们上面那个文件上传接口是可以正常使用的,文档中的参数类型 form-data
是对的)
但是,新版本的 OpenAPI(Swagger3) 在文件上传方面存在问题,在 Postman 中测试文件上传功能正确后,Swagger3 中无法正确识别上传参数的类型 form-data
,它显示的是 String
。
这导致了文件类型上传不成功。(Postman 测试是成功的,Swagger3 反而不成功,可见 Swagger3 也不是很可信)
这个问题笔者尝试了多种方法后尚未解决,期待好心人的告知。
十、MyBatisPlus
MyBatisPlus 是一个 MyBatis 的增强工具,旨在简化开发、提高效率。它在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。接下来我们在 Spring Boot 项目中尝试使用 MyBatisPlus。
1. 添加 Maven 依赖
在 pom.xml
文件中添加。
Lombok
Lombok 是一个 Java 库,使用 Lombok 可以简化实体类的代码,例如自动生成 getter
和 setter
方法、构造函数、toString
方法等,可以减少样板代码的编写。
<!-- lombok 用于自动 getter/setter -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.34</version><scope>provided</scope>
</dependency>
MyBatisPlus 需要的依赖
<!-- MyBatisPlus-->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.8</version>
</dependency><!-- MySQL 驱动-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version>
</dependency><!-- 数据连接池 druid -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.23</version>
</dependency>
2. ORM 框架
ORM(Object-Relational Mapping,对象关系映射)是一种编程技术,用于将对象模型与关系型数据库模型之间进行转换。简单来说,ORM 允许开发者使用面向对象的编程语言(如 Java)来操作数据库,而不需要直接编写 SQL 语句。这种方式可以提高开发效率,减少错误,并且使代码更易于维护。
MyBatis 是一个非常强大且灵活的 ORM 框架,特别适合需要精细控制 SQL 语句的场景。
配置数据库
在配置文件 application.properties
中配置数据库
# 数据库配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 用 jdbc 连接数据库
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# mydb 对应本地 mysql 的数据库
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=false
spring.datasource.username=数据库账号
spring.datasource.password=数据库密码
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
创建数据表
使用 Navicat 工具连接本地 MySQL 数据库,新建数据库 mydb
,在 mydb
中创建数据表 user
。
表结构如下,可以新建查询,运行 sql
文件直接生成。
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`birthday` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;
然后插入一些测试数据
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'zhangsan', '123456', '2020-01-13');
INSERT INTO `user` VALUES (2, 'lisi', '123', '2020-03-13');
INSERT INTO `user` VALUES (3, 'wangwu', '14567', '2023-07-09');
在 Navicat 中新建查询,选择对应的数据库,写入 sql
语句,然后点击运行。
修改实体类
@Data
是 Lombok 提供的一个非常强大的注解,它可以一次性给类生成多个常用的方法,从而大大减少样板代码的编写。具体来说,@Data
注解会为类中的每个字段自动生成以下这些方法:
- Getter 方法:为每个字段生成
get
方法。 - Setter 方法:为每个字段生成
set
方法。 - toString 方法:生成一个
toString
方法,包含所有字段的信息。 - equals 和 hashCode 方法:生成
equals
和hashCode
方法,用于比较对象是否相等。 - RequiredArgsConstructor:生成一个包含所有
final
字段和@NonNull
字段的构造函数。
实体类 User 对应数据库 MySQL 中的 user 表,user 表中的字段与 User 中的变量一一对应。
- 表名与类名应该一致;
- 如果表名与类名不一致,使用
@TableName
注解进行匹配; @TableId
注解用于标记实体类中的主键字段,如id
;IdType.AUTO
表示自增。
配合添加的 Lombok 使用,我们修改一下 User 类,使用 @Data
注解后,实体类中的样板代码明显减少。
/*** @TableName("user")* 如果表名与类名不一致,使用该注解进行匹配*/
@Data // Lombok 会自动生成相关 Getter/Setter 方法,以及 toString 方法
public class User {/*** @TableId 该注解用于标记实体类中的主键字段* IdType.AUTO 自增*/@TableId(type = IdType.AUTO)private int id;private String username;private String password;private String birthday;}
3. 修改主启动类
在主启动文件中添加 @MapperScan
注解,扫描 mapper
包。
UserMapper
接口就放在 mapper
包下面,记得要写一整串的包名,建议右键 Copy Reference。
4. 创建 Mapper 接口
创建 UserMapper 接口:
- 添加注解
@Mapper
- 继承
BaseMapper<User>
接口,根据传入的 User 自动匹配数据库中同名的表。 - MyBatis-Plus 提供了丰富的 CRUD(增删改查) 操作。
- 可以调用
BaseMapper
中写好的方法,实现数据增删改查。一些 SQL 语句不需要自己重复写了。
@Mapper
public interface UserMapper extends BaseMapper<User> {/*** 继承 BaseMapper<User>* 根据传入的 User 自动匹配数据库中同名的表* 可以调用 BaseMapper 中写好的方法,实现数据增删改查*/}
5. 创建 Controller 层
创建 UserController2:响应前端网络请求。
在项目中,我们使用 MybatisPlus 提供的两个操作:
- 查询:调用
BaseMapper
中的selectList()
方法,null
表示没有查询条件,也就是查询全部记录的意思。 - 插入数据:
- 调用
BaseMapper
中的insert()
方法,user
对象中的数据是前端发送过来的,需要插入到底层的数据库表中; id
是自增字段,不需要插入,会自己生成;- 由于
User
类的id
使用了注解标注为主键和自增,当我们在控制台打印user
对象时,会自动读取数据表中的id
字段。这就是为什么前端请求中没有参数id
,后端控制台却能打印出id
的原因。
- 调用
BaseMapper
是自带的,UserMapper
继承了这个接口,子类对象可以使用父类中的一些方法。
@RestController
public class UserController2 {@Autowired // spring 注入 UserMapperprivate UserMapper userMapper;// 查询@GetMapping("/user2")public List<User> query() {List<User> list = userMapper.selectList(null);System.out.println(list);return list;}// 插入数据@PostMapping("/user2")public String save(User user) {// 当数据插入成功后,insert 方法的返回值大于 0int i = userMapper.insert(user);// 虽然没有插入 id,但使用注解标注为主键和自增,user 中会读取数据表中的 id,然后打印到控制台上System.out.println(user);if (i > 0) {return "插入成功";} else {return "插入失败";}}
}
6. 测试方法
使用 Postman 工具对这两个 API 接口进行测试。
查找测试
新增测试
(全文完)
参考资料
上篇速递 - Spring Boot 3 + MybatisPlus:https://blog.csdn.net/Sareur_1879/article/details/143181790
参考学习视频 - SpringBoot + Vue 全栈开发:https://www.bilibili.com/video/BV1nV4y1s7ZN/
参考文章 - Java 开发环境搭建:https://blog.csdn.net/Sareur_1879/article/details/137963848
参考文章 - Maven 在 IDEA 中的配置与使用:https://blog.csdn.net/Sareur_1879/article/details/143091933
参考文章 - Spring Boot 项目是两种创建方式:https://blog.csdn.net/Sareur_1879/article/details/139201323
Maven 官网:https://maven.apache.org/download.cgi
MVN 仓库:https://mvnrepository.com/
Spring Boot 官网:https://spring.io/projects/spring-boot
Oracle 官网:https://www.oracle.com
IDEA 官网:https://www.jetbrains.com.cn/idea/
Postman 官网:https://www.postman.com/
Notepad++ 下载地址:https://notepad-plus.en.softonic.com/
WinRAR 下载地址:https://www.winrar.com.cn/