从零开始学 Spring Boot:30分钟构建你的第一个 RESTful API
本文适合有一定 Java 基础的开发者,通过一个完整的待办事项管理 API 项目,带你快速入门 Spring Boot。
📚 前言
Spring Boot 是 Spring 框架的扩展,它简化了 Spring 应用的初始搭建以及开发过程。通过自动配置和起步依赖,我们可以快速构建生产级别的应用,而无需编写大量样板代码。
为什么选择 Spring Boot?
- 🚀 快速开发:内嵌Tomcat服务器,无需部署 WAR 文件
- ⚙️ 自动配置:零配置或最小配置即可运行
- 📦 起步依赖:通过 Maven/Gradle 依赖管理,简化依赖配置
- 🎯 生产就绪:内置监控、健康检查等功能
🛠️ 环境准备
在开始之前,确保你的开发环境已安装:
- JDK 8+(推荐 JDK 11 或 17)
- Maven 3.6+ 或 Gradle 6+
- IDE:IntelliJ IDEA(推荐)或 Eclipse
- Postman 或 curl(用于测试 API)
检查环境:
bash
java -version
mvn -version
🎯 第一步:创建 Spring Boot 项目
方式一:使用 Spring Initializr(推荐)
-
选择以下配置:
- Project: Maven
- Language: Java
- Spring Boot: 2.7.x 或 3.x
- Group: com.example
- Artifact: todo-api
- Dependencies :
- Spring Web
- Spring Data JPA
- H2 Database(或 MySQL Driver)
-
点击 "Generate" 下载项目
方式二:使用 IDE 创建
IntelliJ IDEA:
- File → New → Project
- 选择 Spring Initializr
- 填写项目信息,选择依赖
- 点击 Finish
📁 项目结构解析
创建完成后,你会看到以下目录结构:
bash
todo-api/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/todoapi/
│ │ │ └── TodoApiApplication.java # 主启动类
│ │ └── resources/
│ │ └── application.properties # 配置文件
│ └── test/ # 测试代码
└── pom.xml # Maven 依赖配置
核心文件说明
1. pom.xml - Maven 依赖管理
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 继承 Spring Boot 父项目 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>todo-api</artifactId>
<version>1.0.0</version>
<name>Todo API</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Web:构建 RESTful API -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA:数据访问 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 数据库:内存数据库,适合开发测试 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok:简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. TodoApiApplication.java - 主启动类
java
package com.example.todoapi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TodoApiApplication {
public static void main(String[] args) {
SpringApplication.run(TodoApiApplication.class, args);
}
}
关键注解说明:
@SpringBootApplication:这是一个组合注解,包含:@Configuration:标识这是一个配置类@EnableAutoConfiguration:启用自动配置@ComponentScan:自动扫描组件
🎨 第二步:理解 Spring Boot 核心概念
1. 自动配置(Auto Configuration)
Spring Boot 会根据类路径中的依赖自动配置应用。例如:
- 如果类路径中有 H2 数据库,会自动配置数据源
- 如果类路径中有 Spring Web,会自动配置 Web 服务器(默认 Tomcat)
2. 起步依赖(Starter Dependencies)
起步依赖是一组预定义的依赖,例如:
spring-boot-starter-web:包含 Web 开发所需的所有依赖spring-boot-starter-data-jpa:包含 JPA 数据访问所需的所有依赖
3. 分层架构
Spring Boot 项目通常采用分层架构:
markdown
Controller 层 → Service 层 → Repository 层 → Entity 层
↓ ↓ ↓ ↓
接收请求 业务逻辑 数据访问 数据模型
🏗️ 第三步:构建待办事项 API
让我们构建一个完整的待办事项管理 API,包含以下功能:
- 创建待办事项
- 查询所有待办事项
- 根据 ID 查询待办事项
- 更新待办事项
- 删除待办事项
- 标记待办事项为完成
1. 创建实体类(Entity)
实体类对应数据库表,使用 JPA 注解:
java
package com.example.todoapi.entity;
import lombok.Data;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "todos")
@Data
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String title;
@Column(length = 1000)
private String description;
@Column(nullable = false)
private Boolean completed = false;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// 在保存前自动设置创建时间
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
// 在更新前自动设置更新时间
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
注解说明:
@Entity:标识这是一个 JPA 实体@Table:指定数据库表名@Id:标识主键@GeneratedValue:主键生成策略(IDENTITY 表示自增)@Column:指定列属性@PrePersist:保存前执行@PreUpdate:更新前执行@Data:Lombok 注解,自动生成 getter/setter/toString 等方法
2. 创建 Repository 接口
Repository 负责数据访问,继承 JpaRepository 即可获得基本的 CRUD 方法:
java
package com.example.todoapi.repository;
import com.example.todoapi.entity.Todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface TodoRepository extends JpaRepository<Todo, Long> {
// 根据完成状态查询
List<Todo> findByCompleted(Boolean completed);
// 根据标题模糊查询
List<Todo> findByTitleContaining(String title);
}
方法命名规则:
findBy+ 属性名:根据属性查询findBy+ 属性名 +Containing:模糊查询- Spring Data JPA 会根据方法名自动生成 SQL
3. 创建 Service 层
Service 层包含业务逻辑:
java
package com.example.todoapi.service;
import com.example.todoapi.entity.Todo;
import com.example.todoapi.repository.TodoRepository;
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
public class TodoService {
@Autowired
private TodoRepository todoRepository;
/**
* 获取所有待办事项
*/
public List<Todo> getAllTodos() {
return todoRepository.findAll();
}
/**
* 根据 ID 获取待办事项
*/
public Todo getTodoById(Long id) {
Optional<Todo> todo = todoRepository.findById(id);
if (todo.isPresent()) {
return todo.get();
} else {
throw new RuntimeException("待办事项不存在,ID: " + id);
}
}
/**
* 创建待办事项
*/
@Transactional
public Todo createTodo(Todo todo) {
return todoRepository.save(todo);
}
/**
* 更新待办事项
*/
@Transactional
public Todo updateTodo(Long id, Todo todoDetails) {
Todo todo = getTodoById(id);
todo.setTitle(todoDetails.getTitle());
todo.setDescription(todoDetails.getDescription());
todo.setCompleted(todoDetails.getCompleted());
return todoRepository.save(todo);
}
/**
* 删除待办事项
*/
@Transactional
public void deleteTodo(Long id) {
Todo todo = getTodoById(id);
todoRepository.delete(todo);
}
/**
* 标记为完成
*/
@Transactional
public Todo markAsCompleted(Long id) {
Todo todo = getTodoById(id);
todo.setCompleted(true);
return todoRepository.save(todo);
}
/**
* 根据完成状态查询
*/
public List<Todo> getTodosByStatus(Boolean completed) {
return todoRepository.findByCompleted(completed);
}
}
注解说明:
@Service:标识这是一个服务类@Autowired:自动注入依赖(Spring 的依赖注入)@Transactional:标识方法需要事务支持
4. 创建 Controller 层
Controller 层处理 HTTP 请求:
java
package com.example.todoapi.controller;
import com.example.todoapi.entity.Todo;
import com.example.todoapi.service.TodoService;
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.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/todos")
public class TodoController {
@Autowired
private TodoService todoService;
/**
* 获取所有待办事项
* GET /api/todos
*/
@GetMapping
public ResponseEntity<Map<String, Object>> getAllTodos(
@RequestParam(required = false) Boolean completed) {
try {
List<Todo> todos;
if (completed != null) {
todos = todoService.getTodosByStatus(completed);
} else {
todos = todoService.getAllTodos();
}
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("data", todos);
response.put("count", todos.size());
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(createErrorResponse(e.getMessage()));
}
}
/**
* 根据 ID 获取待办事项
* GET /api/todos/{id}
*/
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> getTodoById(@PathVariable Long id) {
try {
Todo todo = todoService.getTodoById(id);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("data", todo);
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(createErrorResponse(e.getMessage()));
}
}
/**
* 创建待办事项
* POST /api/todos
*/
@PostMapping
public ResponseEntity<Map<String, Object>> createTodo(@RequestBody Todo todo) {
try {
Todo createdTodo = todoService.createTodo(todo);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "待办事项创建成功");
response.put("data", createdTodo);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(createErrorResponse(e.getMessage()));
}
}
/**
* 更新待办事项
* PUT /api/todos/{id}
*/
@PutMapping("/{id}")
public ResponseEntity<Map<String, Object>> updateTodo(
@PathVariable Long id,
@RequestBody Todo todoDetails) {
try {
Todo updatedTodo = todoService.updateTodo(id, todoDetails);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "待办事项更新成功");
response.put("data", updatedTodo);
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(createErrorResponse(e.getMessage()));
}
}
/**
* 删除待办事项
* DELETE /api/todos/{id}
*/
@DeleteMapping("/{id}")
public ResponseEntity<Map<String, Object>> deleteTodo(@PathVariable Long id) {
try {
todoService.deleteTodo(id);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "待办事项删除成功");
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(createErrorResponse(e.getMessage()));
}
}
/**
* 标记为完成
* PATCH /api/todos/{id}/complete
*/
@PatchMapping("/{id}/complete")
public ResponseEntity<Map<String, Object>> markAsCompleted(@PathVariable Long id) {
try {
Todo todo = todoService.markAsCompleted(id);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "待办事项已标记为完成");
response.put("data", todo);
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(createErrorResponse(e.getMessage()));
}
}
/**
* 创建错误响应
*/
private Map<String, Object> createErrorResponse(String message) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", message);
return response;
}
}
注解说明:
@RestController:组合了@Controller和@ResponseBody,返回 JSON 数据@RequestMapping:定义基础路径@GetMapping:处理 GET 请求@PostMapping:处理 POST 请求@PutMapping:处理 PUT 请求@DeleteMapping:处理 DELETE 请求@PatchMapping:处理 PATCH 请求@PathVariable:获取路径变量@RequestParam:获取查询参数@RequestBody:获取请求体(JSON)
5. 配置 application.properties
在 src/main/resources/application.properties 中添加配置:
properties
# 服务器配置
server.port=8080
# 应用名称
spring.application.name=todo-api
# H2 数据库配置
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# JPA 配置
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# H2 控制台(开发环境使用)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
配置说明:
server.port:服务器端口spring.jpa.hibernate.ddl-auto=update:自动更新数据库表结构spring.jpa.show-sql=true:显示 SQL 语句(开发环境)spring.h2.console.enabled=true:启用 H2 控制台,可通过浏览器访问数据库
🚀 第四步:运行和测试
1. 运行应用
方式一:使用 IDE
- 右键点击
TodoApiApplication.java→ Run
方式二:使用 Maven
bash
mvn spring-boot:run
方式三:打包后运行
bash
mvn clean package
java -jar target/todo-api-1.0.0.jar
启动成功后,你会看到类似输出:
Started TodoApiApplication in 2.345 seconds
2. 测试 API
使用 curl 测试
创建待办事项:
bash
curl -X POST http://localhost:8080/api/todos \
-H "Content-Type: application/json" \
-d '{
"title": "学习 Spring Boot",
"description": "完成 Spring Boot 入门教程",
"completed": false
}'
获取所有待办事项:
bash
curl http://localhost:8080/api/todos
根据 ID 获取待办事项:
bash
curl http://localhost:8080/api/todos/1
更新待办事项:
bash
curl -X PUT http://localhost:8080/api/todos/1 \
-H "Content-Type: application/json" \
-d '{
"title": "学习 Spring Boot(已更新)",
"description": "完成 Spring Boot 入门教程",
"completed": false
}'
标记为完成:
bash
curl -X PATCH http://localhost:8080/api/todos/1/complete
删除待办事项:
bash
curl -X DELETE http://localhost:8080/api/todos/1
使用 Postman 测试
- 打开 Postman
- 创建新的请求
- 选择请求方法(GET、POST、PUT、DELETE)
- 输入 URL:
http://localhost:8080/api/todos - 对于 POST/PUT 请求,在 Body 中选择 raw → JSON,输入 JSON 数据
- 点击 Send
3. 访问 H2 控制台
- 打开浏览器,访问:
http://localhost:8080/h2-console - 连接信息:
- JDBC URL:
jdbc:h2:mem:testdb - 用户名:
sa - 密码: (留空)
- JDBC URL:
- 点击 Connect,可以查看数据库中的数据
📝 第五步:添加参数验证
让我们为 API 添加参数验证,确保数据的有效性:
1. 添加验证依赖
在 pom.xml 中添加:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. 更新实体类
java
package com.example.todoapi.entity;
import lombok.Data;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
@Entity
@Table(name = "todos")
@Data
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "标题不能为空")
@Size(max = 200, message = "标题长度不能超过200个字符")
@Column(nullable = false, length = 200)
private String title;
@Size(max = 1000, message = "描述长度不能超过1000个字符")
@Column(length = 1000)
private String description;
@Column(nullable = false)
private Boolean completed = false;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
3. 更新 Controller
java
@PostMapping
public ResponseEntity<Map<String, Object>> createTodo(
@Valid @RequestBody Todo todo) { // 添加 @Valid 注解
try {
Todo createdTodo = todoService.createTodo(todo);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "待办事项创建成功");
response.put("data", createdTodo);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(createErrorResponse(e.getMessage()));
}
}
4. 添加全局异常处理
创建异常处理类:
java
package com.example.todoapi.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理参数验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidationException(
MethodArgumentNotValidException e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "参数验证失败");
Map<String, String> errors = new HashMap<>();
e.getBindingResult().getFieldErrors().forEach(error -> {
errors.put(error.getField(), error.getDefaultMessage());
});
response.put("errors", errors);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
/**
* 处理运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Map<String, Object>> handleRuntimeException(
RuntimeException e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
}
注解说明:
@ControllerAdvice:全局异常处理@ExceptionHandler:处理特定异常
🎯 核心概念总结
1. 依赖注入(Dependency Injection)
Spring 通过 @Autowired 自动注入依赖,无需手动创建对象:
java
@RestController
public class TodoController {
@Autowired
private TodoService todoService; // Spring 自动注入
}
2. 注解驱动开发
Spring Boot 大量使用注解,减少配置:
| 注解 | 作用 |
|---|---|
@SpringBootApplication |
启动类 |
@RestController |
REST 控制器 |
@Service |
服务层 |
@Repository |
数据访问层 |
@Entity |
JPA 实体 |
@Autowired |
依赖注入 |
@Transactional |
事务管理 |
3. RESTful API 设计
遵循 REST 规范:
| HTTP 方法 | 用途 | 示例 |
|---|---|---|
| GET | 查询 | GET /api/todos |
| POST | 创建 | POST /api/todos |
| PUT | 更新 | PUT /api/todos/{id} |
| DELETE | 删除 | DELETE /api/todos/{id} |
| PATCH | 部分更新 | PATCH /api/todos/{id}/complete |
🔧 常用配置
application.properties vs application.yml
application.properties(键值对格式):
properties
server.port=8080
spring.application.name=todo-api
application.yml(YAML 格式,更易读):
yaml
server:
port: 8080
spring:
application:
name: todo-api
多环境配置
创建不同环境的配置文件:
application-dev.properties:开发环境application-prod.properties:生产环境
在 application.properties 中指定激活的环境:
properties
spring.profiles.active=dev
🚨 常见问题
1. 端口被占用
错误信息: Port 8080 is already in use
解决方案:
- 修改端口:
server.port=8081 - 或关闭占用端口的程序
2. 数据库连接失败
检查:
- 数据库配置是否正确
- 数据库服务是否启动
- 用户名密码是否正确
3. 找不到主类
解决方案:
- 确保
@SpringBootApplication注解在主类上 - 检查包结构是否正确
📚 下一步学习
- 连接 MySQL 数据库:替换 H2 数据库
- 添加用户认证:使用 Spring Security
- 添加日志:使用 Logback 或 Log4j2
- 单元测试:使用 JUnit 和 Mockito
- API 文档:使用 Swagger/OpenAPI
- 部署应用:Docker 容器化
🎓 总结
通过本文,你学会了:
✅ 创建 Spring Boot 项目
✅ 理解分层架构(Controller → Service → Repository → Entity)
✅ 使用 JPA 进行数据访问
✅ 构建 RESTful API
✅ 参数验证和异常处理
✅ 配置应用
关键要点:
- Spring Boot 通过自动配置简化开发
- 分层架构使代码更清晰、易维护
- 注解驱动开发减少样板代码
- RESTful API 设计遵循 HTTP 规范