一、核心概念
REST(Representational State Transfer)是一种基于 HTTP 协议的软件架构风格,用于构建可伸缩、松耦合的 Web 服务。Spring Boot 通过 Spring Web MVC 模块提供了强大的支持来构建 RESTful APIs。
核心概念
资源 (Resource):
- REST 的核心是“资源”。任何可以被命名的东西都可以是一个资源,如用户 (
User
)、订单 (Order
)、产品 (Product
)。 - 资源通过 URI (Uniform Resource Identifier) 来唯一标识,例如
/api/users
,/api/users/123
。
- REST 的核心是“资源”。任何可以被命名的东西都可以是一个资源,如用户 (
HTTP 方法 (HTTP Methods / Verbs):
- 使用标准的 HTTP 方法来对资源执行 CRUD (Create, Read, Update, Delete) 操作:
GET
: 读取/获取资源。安全且幂等。POST
: 创建新资源。非幂等(每次调用可能创建新资源)。PUT
: 完全更新一个已知资源(或创建)。幂等(多次执行效果相同)。PATCH
: 部分更新一个已知资源。非幂等(但通常设计为幂等)。DELETE
: 删除一个资源。幂等。
- 使用标准的 HTTP 方法来对资源执行 CRUD (Create, Read, Update, Delete) 操作:
状态码 (HTTP Status Codes):
- 服务器通过 HTTP 状态码告知客户端请求的结果。
- 常用状态码:
200 OK
: 请求成功(GET
,PUT
,PATCH
,DELETE
)。201 Created
: 资源创建成功(POST
),响应体通常包含新资源。204 No Content
: 请求成功,但响应体无内容(常见于DELETE
,PUT
)。400 Bad Request
: 客户端请求语法错误或参数无效。401 Unauthorized
: 未认证(缺少或无效凭据)。403 Forbidden
: 已认证,但无权限访问。404 Not Found
: 请求的资源不存在。405 Method Not Allowed
: 请求方法不被允许。409 Conflict
: 请求与当前资源状态冲突(如创建已存在的资源)。422 Unprocessable Entity
: 语义错误(如验证失败)。500 Internal Server Error
: 服务器内部错误。
请求/响应体 (Request/Response Body):
- 通常使用 JSON 格式传输数据。Spring Boot 默认使用 Jackson 库进行序列化/反序列化。
- 请求头
Content-Type
指定请求体格式(如application/json
)。 - 响应头
Content-Type
指定响应体格式。
无状态 (Stateless):
- REST 要求服务器不保存客户端状态。每个请求必须包含处理该请求所需的所有信息(如通过
Authorization
头传递令牌)。
- REST 要求服务器不保存客户端状态。每个请求必须包含处理该请求所需的所有信息(如通过
HATEOAS (Hypermedia as the Engine of Application State):
- 可选原则。在响应中包含指向相关资源的链接(
_links
),使客户端能发现 API 的导航路径。
- 可选原则。在响应中包含指向相关资源的链接(
二、操作步骤(非常详细)
我们将构建一个管理 User
资源的 REST API。
步骤 1:创建 Spring Boot 项目
- 访问 Spring Initializr。
- 选择:
- Project: Maven / Gradle
- Language: Java
- Spring Boot: 最新稳定版 (如 3.x)
- Project Metadata:
- Group:
com.example
- Artifact:
rest-demo
- Name:
rest-demo
- Package name:
com.example.restdemo
- Packaging:
Jar
- Java: 17 (或 11, 17+ 推荐)
- Group:
- 在 Dependencies 中添加:
Spring Web
(核心 Web 支持)Spring Data JPA
(持久化,可选,用于演示数据库交互)H2 Database
(内存数据库,用于演示,可选)Spring Boot DevTools
(开发工具,可选)
- 点击 "Generate" 下载项目,解压并用 IDE (如 IntelliJ IDEA) 打开。
步骤 2:项目结构概览
rest-demo/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.example.restdemo/
│ │ │ ├── RestDemoApplication.java // 主启动类
│ │ │ ├── controller/ // Controller 层
│ │ │ ├── model/ // 实体/数据模型
│ │ │ ├── repository/ // 数据访问层 (JPA)
│ │ │ └── service/ // 业务逻辑层
│ │ └── resources/
│ │ ├── application.properties // 配置文件
│ │ └── static/ // 静态资源
│ │ └── templates/ // 模板
└── pom.xml / build.gradle
步骤 3:定义数据模型 (Model)
创建 User
实体类。
// src/main/java/com/example/restdemo/model/User.java
package com.example.restdemo.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
@Entity // JPA 注解,标记为数据库实体
@Table(name = "users") // 指定数据库表名
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 主键自增
private Long id;
@NotBlank(message = "First name is mandatory")
@Size(max = 50)
@Column(nullable = false, length = 50)
private String firstName;
@NotBlank(message = "Last name is mandatory")
@Size(max = 50)
@Column(nullable = false, length = 50)
private String lastName;
@NotBlank(message = "Email is mandatory")
@Email(message = "Email should be valid")
@Size(max = 100)
@Column(nullable = false, unique = true, length = 100)
private String email;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
// Constructors
public User() {
}
public User(String firstName, String lastName, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
// JPA Callbacks for timestamps
@PrePersist
protected void onCreate() {
LocalDateTime now = LocalDateTime.now();
createdAt = now;
updatedAt = now;
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// toString, equals, hashCode (可选,用于调试)
@Override
public String toString() {
return "User{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}
步骤 4:定义数据访问层 (Repository)
创建 UserRepository
接口。
// src/main/java/com/example/restdemo/repository/UserRepository.java
package com.example.restdemo.repository;
import com.example.restdemo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository // 标记为 Spring Bean (可选,JpaRepository 已包含)
public interface UserRepository extends JpaRepository<User, Long> {
// Spring Data JPA 自动提供 findById, save, deleteById, findAll 等方法
// 自定义查询方法:根据 email 查找用户
Optional<User> findByEmail(String email);
}
步骤 5:定义业务逻辑层 (Service)
创建 UserService
接口和实现类。
// src/main/java/com/example/restdemo/service/UserService.java
package com.example.restdemo.service;
import com.example.restdemo.model.User;
import java.util.List;
import java.util.Optional;
public interface UserService {
List<User> getAllUsers();
Optional<User> getUserById(Long id);
User createUser(User user);
User updateUser(Long id, User userDetails);
void deleteUser(Long id);
}
// src/main/java/com/example/restdemo/service/UserServiceImpl.java
package com.example.restdemo.service;
import com.example.restdemo.exception.ResourceNotFoundException;
import com.example.restdemo.model.User;
import com.example.restdemo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Service // 标记为 Spring Service Bean
@Transactional // 声明式事务管理
public class UserServiceImpl implements UserService {
@Autowired // 或使用构造函数注入 (推荐)
private UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public List<User> getAllUsers() {
return userRepository.findAll();
}
@Override
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
@Override
public User createUser(User user) {
// 检查 email 是否已存在
if (userRepository.findByEmail(user.getEmail()).isPresent()) {
throw new RuntimeException("Email already exists");
}
return userRepository.save(user);
}
@Override
public User updateUser(Long id, User userDetails) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
// 更新字段
user.setFirstName(userDetails.getFirstName());
user.setLastName(userDetails.getLastName());
user.setEmail(userDetails.getEmail());
// 注意:createdAt 不应被更新
return userRepository.save(user);
}
@Override
public void deleteUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
userRepository.delete(user);
}
}
步骤 6:创建异常类 (Exception)
创建自定义异常 ResourceNotFoundException
。
// src/main/java/com/example/restdemo/exception/ResourceNotFoundException.java
package com.example.restdemo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND) // 自动设置 HTTP 状态码
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
步骤 7:创建全局异常处理器 (Global Exception Handler)
创建 GlobalExceptionHandler
来统一处理异常。
// src/main/java/com/example/restdemo/exception/GlobalExceptionHandler.java
package com.example.restdemo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice // 应用于所有 @Controller 或 @RestController
public class GlobalExceptionHandler {
// 处理自定义的 ResourceNotFoundException
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Map<String, Object>> handleResourceNotFoundException(ResourceNotFoundException ex) {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("timestamp", System.currentTimeMillis());
errorResponse.put("status", HttpStatus.NOT_FOUND.value());
errorResponse.put("error", "Not Found");
errorResponse.put("message", ex.getMessage());
errorResponse.put("path", "/api/users"); // 可以更精确地获取请求路径
return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}
// 处理 @Valid 校验失败的异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("timestamp", System.currentTimeMillis());
errorResponse.put("status", HttpStatus.UNPROCESSABLE_ENTITY.value());
errorResponse.put("error", "Validation Failed");
errorResponse.put("message", "Input validation failed");
// 收集所有字段错误
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
errorResponse.put("details", errors);
return new ResponseEntity<>(errorResponse, HttpStatus.UNPROCESSABLE_ENTITY);
}
// 处理其他未预期的异常 (生产环境应更谨慎)
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex) {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("timestamp", System.currentTimeMillis());
errorResponse.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
errorResponse.put("error", "Internal Server Error");
errorResponse.put("message", "An unexpected error occurred");
// 生产环境不要暴露 ex.getMessage() 或堆栈
// errorResponse.put("details", ex.getMessage());
return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
步骤 8:创建 REST Controller
创建 UserController
。
// src/main/java/com/example/restdemo/controller/UserController.java
package com.example.restdemo.controller;
import com.example.restdemo.model.User;
import com.example.restdemo.service.UserService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController // 组合注解:@Controller + @ResponseBody
@RequestMapping("/api/users") // 基础路径
public class UserController {
@Autowired // 或使用构造函数注入 (推荐)
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
// GET /api/users - 获取所有用户
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return new ResponseEntity<>(users, HttpStatus.OK);
}
// GET /api/users/{id} - 根据 ID 获取单个用户
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
Optional<User> user = userService.getUserById(id);
return user.map(value -> new ResponseEntity<>(value, HttpStatus.OK))
.orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
// 或者直接抛出异常,由 GlobalExceptionHandler 处理
// User user = userService.getUserById(id)
// .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
// return ResponseEntity.ok(user);
}
// POST /api/users - 创建新用户
@PostMapping
@ResponseStatus(HttpStatus.CREATED) // 直接设置状态码
public User createUser(@Valid @RequestBody User user) {
// @Valid 触发 Bean Validation
return userService.createUser(user);
}
// PUT /api/users/{id} - 完全更新用户
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @Valid @RequestBody User userDetails) {
User updatedUser = userService.updateUser(id, userDetails);
return ResponseEntity.ok(updatedUser);
}
// PATCH /api/users/{id} - 部分更新用户 (示例:只更新 email)
@PatchMapping("/{id}")
public ResponseEntity<User> partialUpdateUser(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
// 注意:这里简化处理,实际项目中可能需要更精细的逻辑或使用专门的 DTO
Optional<User> userOpt = userService.getUserById(id);
if (userOpt.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
User user = userOpt.get();
// 示例:只允许更新 email
if (updates.containsKey("email")) {
String newEmail = (String) updates.get("email");
// 简单验证 (实际应用中应更严格)
if (newEmail != null && !newEmail.trim().isEmpty()) {
user.setEmail(newEmail);
}
}
// ... 可以添加其他字段的更新逻辑
User savedUser = userService.createUser(user); // 复用 createService 的 save 方法
return ResponseEntity.ok(savedUser);
}
// DELETE /api/users/{id} - 删除用户
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
步骤 9:配置数据库 (application.properties)
# src/main/resources/application.properties
# --- H2 Database (for demo) ---
# 启用 H2 控制台 (开发环境)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# H2 数据库配置
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# --- JPA / Hibernate ---
# 自动创建/更新表结构
spring.jpa.hibernate.ddl-auto=update
# 显示 SQL
spring.jpa.show-sql=true
# 格式化 SQL
spring.jpa.properties.hibernate.format_sql=true
# 方言
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# --- Server ---
# 服务器端口
server.port=8080
步骤 10:启动应用并测试
- 运行
RestDemoApplication.java
的main
方法。 - 应用启动后,访问
http://localhost:8080/h2-console
(如果使用 H2),连接到jdbc:h2:mem:testdb
。 - 使用
curl
,Postman
, 或httpie
测试 API:
# 创建用户
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"firstName":"John", "lastName":"Doe", "email":"john.doe@example.com"}'
# 获取所有用户
curl -X GET http://localhost:8080/api/users
# 获取单个用户
curl -X GET http://localhost:8080/api/users/1
# 更新用户 (PUT)
curl -X PUT http://localhost:8080/api/users/1 \
-H "Content-Type: application/json" \
-d '{"firstName":"Jane", "lastName":"Doe", "email":"jane.doe@example.com"}'
# 部分更新用户 (PATCH)
curl -X PATCH http://localhost:8080/api/users/1 \
-H "Content-Type: application/json" \
-d '{"email":"new.email@example.com"}'
# 删除用户
curl -X DELETE http://localhost:8080/api/users/1
三、常见错误与解决方案
错误 | 原因 | 解决方案 |
---|---|---|
404 Not Found | URL 路径错误、@RequestMapping 路径拼写错误、Controller 未被扫描到(缺少 @ComponentScan 或包路径不对)。 |
1. 检查 URL 路径和 @RequestMapping /@GetMapping 等注解。2. 确保 Controller 类在主应用类的包或子包下。3. 检查 @RestController 注解。 |
405 Method Not Allowed | 请求方法(GET/POST/PUT/DELETE)与 Controller 方法不匹配。 | 检查 @GetMapping , @PostMapping 等注解是否正确。 |
400 Bad Request | 请求体 JSON 格式错误、缺少必需字段、数据类型不匹配。 | 1. 检查 JSON 语法(逗号、引号)。2. 确保必需字段存在。3. 检查数据类型(字符串用引号,数字不用)。 |
422 Unprocessable Entity | @Valid 校验失败(如 @NotBlank , @Email 不满足)。 |
1. 检查请求体数据是否符合实体类的校验注解要求。2. 查看响应体中的 details 字段获取具体错误信息。 |
500 Internal Server Error | 代码中发生未处理的异常(如空指针、数据库错误)。 | 1. 查看服务器日志获取详细错误堆栈。2. 确保有适当的异常处理(@ExceptionHandler , @RestControllerAdvice )。 |
HTTP Status 200 with empty body | @ResponseBody 缺失(但 @RestController 已包含)、方法返回 void 但期望有响应体。 |
1. 确认使用 @RestController 或 @ResponseBody 。2. 对于 POST 创建资源,应返回 201 Created 和资源本身。 |
Jackson Serialization Error | 实体类有循环引用(如 User 有 List<Order> ,Order 有 User )、缺少无参构造函数、final 字段。 |
1. 使用 @JsonIgnore 或 @JsonManagedReference /@JsonBackReference 处理循环引用。2. 确保实体类有 public 无参构造函数。3. 使用 @JsonProperty 处理 final 字段。 |
四、注意事项
@RestController
vs@Controller
:@RestController
是@Controller
+@ResponseBody
的组合,所有方法返回值都会直接写入响应体。如果使用@Controller
,需要在每个需要返回 JSON 的方法上加@ResponseBody
。- 路径变量 (
@PathVariable
):用于获取 URL 路径中的动态部分(如/users/{id}
中的id
)。 - 请求体 (
@RequestBody
):用于将 HTTP 请求体中的 JSON 数据反序列化为 Java 对象。 - 请求参数 (
@RequestParam
):用于获取 URL 查询参数(如/users?name=john
)。 @Valid
和 Bean Validation:在@RequestBody
参数前加@Valid
可触发 JSR-380 (Hibernate Validator) 校验。确保添加了jakarta.validation
依赖(Spring Boot Web 通常已包含)。ResponseEntity
:提供了对 HTTP 响应的完全控制(状态码、头、体)。@ResponseStatus
可以直接在方法上设置状态码。@Transactional
:在 Service 层使用,确保数据库操作在事务中执行。- API 版本控制:建议在 URL 路径中加入版本号,如
/api/v1/users
,便于未来 API 演进。 - 安全性:生产环境必须集成 Spring Security 进行认证和授权。
- CORS (跨域):如果前端和后端在不同域名/端口,需要配置 CORS。可使用
@CrossOrigin
注解或全局配置。
五、使用技巧
@Data
(Lombok):使用 Lombok 的@Data
注解可以自动生成getter
,setter
,toString
,equals
,hashCode
,减少样板代码。@NoArgsConstructor
,@AllArgsConstructor
(Lombok):生成无参和全参构造函数。@Builder
(Lombok):生成构建者模式。@Slf4j
(Lombok):生成日志记录器log
。@RequestMapping(produces = "application/json")
:指定 Controller 或方法只产生 JSON 响应。@RequestMapping(consumes = "application/json")
:指定 Controller 或方法只消费 JSON 请求。@RequestParam
默认值:@RequestParam(defaultValue = "0") int page
。@RequestHeader
:获取 HTTP 请求头。@CookieValue
:获取 Cookie 值。@MatrixVariable
:处理矩阵变量(较少用)。
六、最佳实践
- 分层架构:清晰分离
Controller
(处理 HTTP)、Service
(业务逻辑)、Repository
(数据访问)。 - 使用 DTO (Data Transfer Object):
- 避免直接暴露实体:不要直接将 JPA 实体作为 API 的请求/响应体返回,尤其是在有敏感字段(如密码)或关联关系时。
- 创建专门的 DTO:为每个 API 操作创建输入 DTO (
UserCreateRequest
,UserUpdateRequest
) 和输出 DTO (UserResponse
)。 - 使用 MapStruct 或 ModelMapper:简化 DTO 与实体之间的转换。
- 统一错误响应格式:使用
@RestControllerAdvice
和@ExceptionHandler
定义统一的错误响应结构(如前面GlobalExceptionHandler
示例)。 - API 文档:集成 Springdoc OpenAPI (Swagger) 自动生成 API 文档。
- 依赖:
springdoc-openapi-starter-webmvc-ui
- 访问:
http://localhost:8080/swagger-ui.html
- 依赖:
- 输入验证:使用
@Valid
和 Bean Validation 注解进行声明式校验。 - 幂等性:确保
PUT
和DELETE
操作是幂等的。 - 资源命名:使用名词(复数形式)表示资源集合,如
/users
,/orders
。避免使用动词。 - HTTP 状态码:正确使用状态码,让客户端能准确理解结果。
- 版本控制:在 URL 路径中包含 API 版本(
/api/v1/users
)。 - 日志记录:在关键操作(如创建、更新、删除)中添加日志。
- 安全性:始终集成 Spring Security,实现认证(如 JWT)和授权。
- HATEOAS (可选):对于成熟的 API,考虑使用 Spring HATEOAS 提供链接。
七、性能优化
- 数据库优化:
- N+1 查询问题:使用
@EntityGraph
,JOIN FETCH
, 或@Query
明确指定关联加载策略(EAGER
谨慎使用)。 - 索引:在经常查询的字段上创建数据库索引。
- 分页:对于返回列表的 API,必须支持分页。使用
Pageable
和Page
。// Controller @GetMapping public ResponseEntity<Page<UserResponse>> getAllUsers(Pageable pageable) { Page<User> usersPage = userService.getAllUsers(pageable); Page<UserResponse> responses = usersPage.map(user -> modelMapper.map(user, UserResponse.class)); return ResponseEntity.ok(responses); }
- N+1 查询问题:使用
- 缓存:
- 使用
@Cacheable
,@CachePut
,@CacheEvict
(Spring Cache Abstraction) 缓存频繁读取且不常变的数据(如配置信息、热点数据)。 - 集成 Redis 作为缓存后端。
- 使用
- 异步处理:
- 对于耗时较长的操作(如发送邮件、生成报告),使用
@Async
将其放入异步线程池执行,立即返回202 Accepted
状态码。
- 对于耗时较长的操作(如发送邮件、生成报告),使用
- GZIP 压缩:在
application.properties
中启用响应压缩。server.compression.enabled=true server.compression.mime-types=application/json,text/html,text/xml,text/plain
- 连接池优化:配置 HikariCP 等连接池参数(
maximumPoolSize
,connectionTimeout
)。 - 避免大对象序列化:避免返回包含大量数据或深层嵌套的对象。使用分页和投影(Projection)。
- 使用
ResponseEntity
的HttpStatus
:直接返回ResponseEntity.status(HttpStatus.CREATED).body(user)
比抛出异常有时更高效(异常处理有开销)。 - 监控:集成 Spring Boot Actuator,监控应用健康、指标(如 HTTP 请求延迟)。
总结
构建标准的 Spring Boot REST API 需要遵循 REST 原则,合理使用 Spring MVC 注解,并注重分层、安全、错误处理和性能。
快速上手关键点:
@RestController
+@RequestMapping
定义控制器。@GetMapping
/@PostMapping
/@PutMapping
/@DeleteMapping
映射 HTTP 方法。@PathVariable
获取路径参数,@RequestBody
接收 JSON 请求体。ResponseEntity
或@ResponseStatus
控制响应。@Valid
+ Bean Validation 进行输入校验。@RestControllerAdvice
+@ExceptionHandler
统一处理异常。- Service 层 处理业务逻辑,Repository 层 访问数据。
- DTO 模式 隔离内外模型。
- API 文档 (Springdoc OpenAPI)。
- 分页 和 缓存 是性能关键。
遵循这些实践,你将能构建出健壮、可维护、高性能的 RESTful 服务。