毫不夸张地说,Spring Boot 已经成为 Java 后端开发里“绕不开的名字”。打开招聘网站、翻开源项目源码、问身边做后端的朋友,你会发现它无处不在。如果你是从零开始的小白,想学后端却又不知道从哪下手,我的建议很直接:选 Spring Boot 作为第一个后端框架,并且用一个完整的小项目把它跑通。这篇文章就围绕 Spring Boot 做一次全栈入门,不预设你有多少 Java 基础,只要求你愿意动手跟着敲。目标是读完以后,你能自己创建一个项目、写接口、连数据库、做一个能点的页面,并且能大概说出这套东西是怎么转起来的。
我见过太多人卡在入门阶段,不是因为笨,而是被碎片化的信息淹没了:今天看一个注解,明天抄一段代码,后天装个环境,始终没有串成一条完整的链路。所以这篇文章会刻意沿着“环境 → 项目 → 接口 → 数据库 → 前端 → 排错”这条主线走,用一个小型待办事项应用串联全部内容。你照着走完,得到的不是一个“hello world”,而是一个能真正跑起来、能增删改查、能从前端点到后端的小系统。
1. 动手之前:先把 Spring Boot 的定位和原理搞明白
1.1 为什么 Spring Boot 能让开发“变简单”
很多新手第一次接触 Spring Boot 时,会被各种概念吓到:IoC、AOP、自动配置、starter……感觉像在看天书。其实你完全不必从这些底层概念开始啃,先理解 Spring Boot 解决了什么现实问题,再回头看这些名词就顺了。
在 Spring Boot 出现之前,用传统 Spring 搭建一个 Java Web 项目是很麻烦的。你需要自己下载一堆依赖 jar 包、手写很多 XML 配置文件、管理 Bean 的创建关系、再把项目部署到独立的 Tomcat 服务器里。这一套流程对老手都算繁琐,对新手更是劝退。Spring Boot 的核心思路可以总结成一句话:把“常用配置”变成“默认约定”,把“复杂依赖”变成“即插即用的 starter”。
举一个特别直观的例子。你要给项目加 Web 开发能力,传统方式要引很多 jar 包、配置 DispatcherServlet、设置 web.xml。而 Spring Boot 只需要在 pom.xml 里加一个依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>这一个 starter 就把 Web 开发所需的核心库、内嵌服务器、自动配置全打包进来了。项目启动后,甚至不需要安装单独的 Tomcat,因为 Spring Boot 默认把 Tomcat 内嵌在程序里,你双击运行一个 main 方法,一个 Web 服务器就起来了。
我用一个生活化类比帮你建立直觉:传统 Spring 就像你搬进毛坯房,要自己改水电、铺地砖、装马桶;Spring Boot 则更像拎包入住的精装房,虽然不能保证每个细节都符合你的审美,但足以让你立刻住下来,后续再按需微调。对小白来说,先住进来、看到效果,比先研究装修工艺重要得多。
1.2 小白的正确打开方式:不要追求一步到位
学习 Spring Boot 有一个常见的误区:一上来就想把 Spring 全家桶学完,今天看 Spring MVC,明天看 Spring Data,后天看 Spring Security,结果一个都没学会。我给你的建议是:先跑通最小闭环,再逐步扩展。
所谓最小闭环就是这样的链路:浏览器发起请求 → 后端接口接收 → 处理逻辑 → 返回数据 → 浏览器展示。先把这条链路走通,哪怕只是返回一个字符串,你对整个系统的运行方式就有了真实体感。然后再往链路里加东西:加数据库、加前端页面、加参数校验、加异常处理。每加一层,你都会清楚地知道这一层是干什么的、加在哪里、为什么需要。
前置知识也不需要想得太难。你只要掌握 Java 的基础语法就够了:类和对象、方法、基本数据类型、集合框架、断言和循环,这些大约花一到两周就能补上。你不需要先去精通 Spring,也不需要先学完数据库原理,因为 Spring Boot 的 starter 机制和自动配置,会替你把大量底层工作先做掉。很多概念你甚至可以先“用起来”,再在踩坑过程中慢慢理解。
2. 环境搭建:把电脑调整到可开发状态
2.1 JDK 版本选择与安装要点
Java 的环境主要就是 JDK(Java Development Kit),它既负责把 Java 源码编译成字节码,也提供运行 Java 程序所需的环境。Spring Boot 对 JDK 版本有一定的要求,不同版本的 Spring Boot 对 Java 版本的支持范围不一样。对新手来说,我的建议是:安装 LTS(长期支持)版本,比如 Java 17,这是目前兼容性最好、最常见的组合之一。
安装 JDK 这件事本身不难,但有三个细节经常把人绊住:
一是环境变量。安装完成后需要配置JAVA_HOME和PATH,否则终端里执行java -version会提示找不到命令。不同操作系统的配置方式不同,Windows 下在系统设置里改环境变量,macOS/Linux 下在 shell 配置文件中 export。配置完记得重新打开终端,再用java -version验证,这一步实测是不能跳的。
二是多个 JDK 版本冲突。如果电脑里之前装过其他版本的 JDK,建议先卸载干净,或者在配置环境变量时格外小心,避免JAVA_HOME指向了旧版本。曾有个学员卡了一下午,代码怎么都对,最后发现终端里默认的 JDK 是历史遗留的老版本,新项目语法根本不被支持。
三是 IDE 里的 JDK 设置要和系统一致。很多时候命令行跑得通,但点击 IDE 里运行按钮报错,就是 IDE 内部配置的 SDK 和项目编译级别不一致导致的。
2.2 Maven 配置与国内镜像加速
Maven 是 Java 项目里最主流的依赖管理和构建工具。你不需要现在就把 Maven 的所有细节研究透,只需理解它的核心逻辑:项目里需要的所有第三方库,都通过 pom.xml 文件声明,Maven 会自动从远程仓库下载这些库,并处理好它们之间的版本依赖关系。
这里有一个新手最容易踩的坑:默认的 Maven 中央仓库在国外,下载依赖极慢,甚至经常超时。你第一次创建 Spring Boot 项目时,哪怕只启动一个最简单的程序,也要下载上百 MB 的依赖,如果不动这个配置,你极有可能在“等待下载”这一步消耗掉全部耐心。
解决方法是在 Maven 的配置文件 settings.xml 里把中央仓库替换成国内镜像。这一步操作不算复杂,但效果立竿见影,我每次帮人装环境都最先做这件事:
<mirror> <id>aliyun</id> <mirrorOf>central</mirrorOf> <name>aliyun maven repository</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>配置好之后,在终端执行mvn -v验证 Maven 是否可用,再执行mvn clean compile测试依赖能否顺利下载。首次构建会比较久,之后依赖会缓存在本地的.m2目录里,第二次就快很多了。
2.3 开发工具与基础设置
开发 Java 项目离不开一个好用的 IDE。市面上的选择很多,但我想给新手的建议很明确:不要在一开始就把精力耗在编辑器插件的选择上,直接用主流的 Java IDE 的社区版即可。社区版免费、开箱即用、对学习项目完全够用。
打开 IDE 以后,建议你先把两个基础设置调好:
一是编码格式统一为 UTF-8。Windows 系统默认编码可能是 GBK,而项目代码、配置文件通常按 UTF-8 编写,如果不统一,运行时会遇到乱码问题,控制台输出也可能异常。这个坑排查起来很耗时,提前设置一分钟能省一小时。
二是 Maven 的配置要指向你刚才改过的 settings.xml。IDE 自带的 Maven 是内置版本,配置文件不一定用的就是你自定义的那一份。在 IDE 设置里找到构建工具的 Maven 配置项,把 User settings file 改成自己的路径。
很多教程会在这个环节花大篇幅教各种快捷键和界面布局,我的建议是:先用默认界面,随着代码量增加,你自然会发现自己需要哪些功能,到那时再优化不迟。学习阶段最重要的不是工具用得飞起,而是把逻辑跑通。
3. 创建并跑通第一个 Spring Boot 项目
3.1 用可视化工具生成项目
第二、三步其实可以反向操作——先用可视化工具生成一个能跑的项目,再回头调环境。Spring 官方提供了一个项目脚手架工具,相当于一个“自动生成器”,你只需要填一些信息、勾选需要的依赖,它就会帮你生成一个完整的项目压缩包。
填写的关键信息包括三部分:
- Group:组织标识,一般按域名倒序写,比如
com.example。这一步对小白影响不大,只要不出现中文和特殊字符就行。 - Artifact:项目名,比如
todo-app。 - 依赖:这里不要贪多,刚开始只需要选择 Spring Web 就够了,之后要连数据库时再加依赖。
生成后的项目结构长这样:
todo-app ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com/example/todoapp │ │ │ └── TodoAppApplication.java │ │ └── resources │ │ ├── application.properties │ │ ├── static │ │ └── templates │ └── test │ └── java这个结构并不复杂。pom.xml是项目的依赖清单和构建配置;TodoAppApplication.java是程序的入口,包含一个main方法;resources目录存放配置文件和静态资源。
打开 IDE,选择导入 Maven 项目,选中生成的文件夹。第一次导入 IDE 会自动读取 pom.xml 并下载依赖,这个过程确实需要一点耐心,但前面如果配置好了国内镜像,一般几分钟内就能完成。
3.2 启动类与自动配置
启动类里的@SpringBootApplication可能是你写的第一个关键注解。这个注解本身是个组合注解,它把三个注解合在了一起:@SpringBootConfiguration声明这是一个配置类,@EnableAutoConfiguration开启自动配置,@ComponentScan让 Spring 自动扫描当前包及其子包下的组件。这就是为什么你新建的 Controller 通常要放在启动类所在的包或子包里面,否则 Spring 根本扫描不到。
你能看到“项目启动后一切自动转起来”的效果,关键就在于@EnableAutoConfiguration。打个比方,你点了外卖,商家自动配好了一整套餐具和调料,你不需要告诉他们具体要什么,也不需要说怎么摆放,这就是“自动配置”:Spring Boot 根据 classpath 下的依赖,推断出你需要什么功能,然后按默认约定装配好。
直接运行启动类的main方法,控制台会出现一段 Spring Boot Logo,然后滚动一堆日志。如果最后出现类似Tomcat started on port 8080的日志,说明你的项目已经成功启动,一个内嵌的 Web 服务器正在 8080 端口上监听请求。
3.3 编写并访问第一个接口
项目能启动只是第一步,还要能响应请求。我先写一个最简单的 Controller,让你感受一下“请求 → 处理 → 响应”这个过程到底有多简单:
package com.example.todoapp; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "Hello Spring Boot"; } }新建这个类,重新运行启动类,然后打开浏览器访问http://localhost:8080/hello。你会在页面上看到Hello Spring Boot这个字符串。
这里有两个注解值得解释一下:@RestController表示这是一个处理 HTTP 请求的控制器类,并且方法返回值会直接写到响应体里;@GetMapping("/hello")表示当浏览器发送一个 GET 请求到/hello路径时,执行下面的方法。
新手常犯的一个错误是改完代码以后忘记重启。Spring Boot 默认不会在你修改代码后自动重新加载,必须重新运行启动类,或者后面配置 devtools 热部署。所以当你看到访问 404、页面没变化时,第一个要检查的就是:程序重启了吗?
4. 从接口到数据库:完成核心业务链路
4.1 分层架构与包结构设计
只写一个 Controller 只能感受 Spring Boot 的“甜”,离实际开发还很远。真实项目里的代码通常要分几层,每层只负责一件事。最经典的分层是 Controller、Service、Repository 三层。
Controller 层是“门面”,负责接收请求、参数校验、把结果返回给前端。Service 层是“业务大脑”,负责处理核心业务逻辑,比如校验数据、计算、组合仓储层的数据。Repository 层是“数据仓库”,负责和数据库打交道,把数据查出来、存进去。
为什么要分这么多层?最直接的答案是职责分离。在一个小项目里,你把所有代码写在一个类里也能跑,但项目一大,任何一处改动都可能引发连锁问题。分层以后,每个类只做一件明确的事,出了问题能快速定位;写单元测试时可以只测 Service 层,不需要启动整个 Web 环境。这就像一家餐厅,服务员(Controller)、后厨(Service)、仓库管理员(Repository)各司其职,生意做大也能有条不紊。
我的建议从一开始就按这个分包结构写代码:
com.example.todoapp ├── controller // 控制层 ├── service // 业务层 ├── repository // 数据访问层 ├── entity // 实体类 └── TodoAppApplication.java4.2 用 Spring Data JPA 操作数据库
在数据库选型这个环节,我推荐先上 H2 内存数据库。H2 是一个用 Java 写的嵌入式数据库,项目启动时自动创建,项目停止时数据消失。好处是零配置、不需要安装任何数据库软件,非常适合先把业务逻辑跑通。等你理解了整个过程,再换成 MySQL 生产环境配置也不迟。
要使用 JPA 和 H2,需要在 pom.xml 里加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>接下来创建一个实体类 Todo,它对应数据库里的一张 todo 表:
package com.example.todoapp.entity; import jakarta.persistence.*; @Entity @Table(name = "todo") public class Todo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String title; private boolean completed; public Todo() { } public Todo(String title, boolean completed) { this.title = title; this.completed = completed; } // getter 和 setter 省略 }事情在这里变得有意思了。你并没有写任何建表 SQL,也没有写任何数据库连接代码,但 Spring Boot 会在启动时自动帮你建表;你也不需要写增删改查的 SQL,Spring Data JPA 会自动生成一个实现。它最神奇的地方在于:你只需要定义一个接口,什么都不用实现,就能获得一大堆现成的方法:
package com.example.todoapp.repository; import com.example.todoapp.entity.Todo; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface TodoRepository extends JpaRepository<Todo, Long> { }这个接口继承 JpaRepository 之后,就有了findById、findAll、save、deleteById等全套 CRUD 方法。你可以把它理解为:Spring 在运行时动态帮你生成了一个实现了这个接口的类,不需要你手动写实现。这种“声明即实现”的体验,我每次用都觉得挺爽的。
4.3 依赖注入与控制反转
到这里,你可能想问:Controller 里怎么用上 Service?Service 里怎么用上 Repository?直接new一个不行吗?当然可以 new,但这样会让对象之间的依赖关系变得僵硬,而且测试的时候很难替换成模拟对象。
Spring 解决这个问题的方式叫控制反转。把所有对象的创建和装配交给 Spring 容器,你需要什么,声明一下,Spring 自动给你注入进来。最常见的注入方式是构造器注入:在 Service 类的构造方法上写@RequiredArgsConstructor等注解,让 Spring 把 Repository 实现类传进来。
package com.example.todoapp.service; import com.example.todoapp.entity.Todo; import com.example.todoapp.repository.TodoRepository; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.List; @Service public class TodoService { private final TodoRepository todoRepository; public TodoService(TodoRepository todoRepository) { this.todoRepository = todoRepository; } public List<Todo> findAll() { return todoRepository.findAll(); } public Todo createTodo(String title) { if (!StringUtils.hasText(title)) { throw new IllegalArgumentException("标题不能为空"); } Todo todo = new Todo(title, false); return todoRepository.save(todo); } public void deleteTodo(Long id) { todoRepository.deleteById(id); } }这段代码里有几个点值得展开说。@Service注解告诉 Spring 这是一个业务层组件,Spring 启动时会自动扫描并注册到容器里。构造器注入这种方式对新手非常友好,因为对象一旦创建,依赖就固定了,不会出现运行到一半才发现某个依赖是 null 的尴尬情况。
4.4 编写完整的 RESTful 接口
有了 Service 层以后,Controller 就变得非常薄,它只做两件事:接收 HTTP 请求里的数据,调用 Service,再把结果包装成 JSON 返回。下面是一组标准的 RESTful 接口:
package com.example.todoapp.controller; import com.example.todoapp.entity.Todo; import com.example.todoapp.service.TodoService; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/todos") public class TodoController { private final TodoService todoService; public TodoController(TodoService todoService) { this.todoService = todoService; } @GetMapping public List<Todo> list() { return todoService.findAll(); } @PostMapping public Todo create(@RequestBody Todo todo) { return todoService.createTodo(todo.getTitle()); } @DeleteMapping("/{id}") public void delete(@PathVariable Long id) { todoService.deleteTodo(id); } }这里涉及 RESTful 风格的设计理念。你可能注意到,我没有把不同的操作放在不同路径里,而是让同一个路径/api/todos根据 HTTP 方法不同而执行不同操作:GET 表示查询全部,POST 表示新增,DELETE 表示删除某个资源。这种设计清晰、可读性好,前端调用时也容易记住规则。
@RequestBody负责把前端传来的 JSON 字符串自动反序列化成 Todo 对象;@PathVariable从 URL 中提取路径参数,比如/api/todos/3里的 3。这背后都是 Spring MVC 在做转换,你只需要关注业务本身。
启动项目后,我建议你用浏览器先访问http://localhost:8080/api/todos,能看到一个空的 JSON 数组。新增功能因为需要发送 POST 请求,浏览器地址栏做不到,可以配合一个接口调试工具,或者用我们下一步要写的前端页面来测。
5. 补全前端页面:一个真正可用的全栈小应用
5.1 前端方案怎么选
后端接口写好了,现在需要一个前端页面去调用它。这里有一个路线选择:模板渲染和前后端分离。
模板渲染的思路是后端返回 HTML 页面,页面的数据由后端填充,典型代表是 Thymeleaf。前后端分离的思路是后端只返回 JSON 数据,前端页面独立部署,用 JavaScript 请求后端接口并动态渲染。对小白来说,我推荐后者的一种简化版:把静态 HTML 文件放在src/main/resources/static目录下,由 Spring Boot 直接托管,页面里的 JavaScript 用 fetch 调用同源的接口。
这样做的好处很明显:你不用考虑部署两个服务,也不用处理跨域问题,因为页面和后端接口在同一个域名、同一个端口下。而负责页面的人还同时能学习 HTTP 请求、JSON 格式、AJAX 交互这些前后端联调的必备知识,性价比很高。
前端文件放在 static 目录下,访问方式很简单。比如src/main/resources/static/index.html,启动项目后,浏览器直接访问http://localhost:8080/index.html就能看到页面。
5.2 页面与接口的联调实现
我写一个尽可能简单的待办事项页面。它的功能有三个:展示待办列表、新增待办、删除待办。HTML 结构没什么特别,核心是 JavaScript 里怎么用 fetch 和后端交互:
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>待办事项</title> </head> <body> <h1>我的待办</h1> <input id="title" type="text" placeholder="输入待办内容"> <button onclick="createTodo()">新增</button> <ul id="list"></ul> <script> async function loadTodos() { const res = await fetch('/api/todos'); const todos = await res.json(); const list = document.getElementById('list'); list.innerHTML = ''; todos.forEach(todo => { const li = document.createElement('li'); li.textContent = todo.title + ' '; const delBtn = document.createElement('button'); delBtn.textContent = '删除'; delBtn.onclick = () => deleteTodo(todo.id); li.appendChild(delBtn); list.appendChild(li); }); } async function createTodo() { const title = document.getElementById('title').value; if (!title.trim()) return; await fetch('/api/todos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: title }) }); document.getElementById('title').value = ''; loadTodos(); } async function deleteTodo(id) { await fetch('/api/todos/' + id, { method: 'DELETE' }); loadTodos(); } loadTodos(); </script> </body> </html>这段代码的逻辑非常直白:页面加载时调用loadTodos,向后端 GET 请求待办列表,拿到 JSON 数组后,循环生成列表项;点新增按钮时,把输入框内容序列化成 JSON 发 POST 请求;点删除按钮时,发 DELETE 请求。
这里有三个地方是新手容易忽略的:一是 fetch 默认发送的请求体如果是 JSON,必须手动设置Content-Type为application/json,否则后端解析时会因为格式不对而报错;二是 POST 的 body 必须用JSON.stringify把对象转成字符串,直接传对象会出问题;三是页面加载完成以后再执行加载函数loadTodos(),如果你把这个函数写在页面脚本顶部,此时页面还没渲染完,虽然这里不影响,但养成等 DOM 就绪再操作的意识很重要。
5.3 联调避坑:数据格式与跨域
前后端联调最常见的失败原因,一个是数据格式对不上,另一个是跨域。
先说数据格式。前端传回后端的数据,字段名一定要和后端实体类的属性名一致。比如后端实体字段叫title,前端就必须传title而不是text或content。如果字段对不上,Spring 反序列化时会得到 null,接口虽然返回成功,但数据是空的,很容易被忽略。
再说跨域。如果你把前端页面单独放在另一个端口启动,而接口在 8080 端口,浏览器出于安全策略会阻止跨域请求。解决方式有很多,比如在 Controller 上加@CrossOrigin,或者配置全局 CORS 规则。但我的建议是:在小项目阶段,直接把页面文件放在 static 目录下,页面和接口始终同源,跨域问题根本不会出现。等你把这套流程玩熟了,再研究跨域也不迟。
6. 新手排错实战:常见问题与解决思路
6.1 端口占用与地址访问异常
启动项目时报错提示Port 8080 was already in use,大概率是上一次启动的程序没有完全退出,或者 8080 端口被其他软件占用。处理方式:任务管理器找到对应 Java 进程结束它,或者干脆换个端口。Spring Boot 修改端口非常简单,在application.properties里写一行:
server.port=8081关于访问地址,还有一个高频困惑:什么时候用localhost,什么时候用127.0.0.1,什么时候用局域网 IP。本机测试时两者都行;想让手机或其他设备访问你电脑上的项目,就需要用电脑的局域网 IP。前提是启动时指定了监听所有网卡地址,Spring Boot 默认是监听的,如果你在配置里改过server.address就要留意了。
6.2 依赖下载慢、缺失或冲突
前文强调了镜像配置,但你还可能遇到另一种情况:项目里引入一个新依赖之后,一直提示找不到类或者版本冲突。这通常意味着 IDEA 的依赖索引没有刷新。解决方式是在 IDE 里触发 Maven 的重新加载,或者到项目目录执行mvn clean install,让 Maven 强制重新解析依赖。
还有一种常见情况是:在pom.xml里手写了依赖,但 IDE 没有下载成功,代码里爆红找不到包。这时可以先检查本地 Maven 仓库的目录结构,看对应 jar 包是否真的存在;如果不存在,删掉本地仓库里对应文件夹再重新加载,实测下来很管用。
版本冲突是更进阶的问题,小白阶段最好的处理方式不是自己研究依赖树,而是尽量使用 Spring Initializr 勾选依赖,让官方替你把推荐版本配好。手写依赖时,优先不加<version>版本号,交给 Spring Boot 的 BOM 统一管理,能省掉非常多的麻烦。
6.3 数据库连接失败与时区问题
很多新手从 H2 切换到自己安装的 MySQL 时,第一反应是“明明密码对了,怎么连不上”。常见原因有三个:
第一是连接 URL 写错。MySQL 的 JDBC URL 格式通常是jdbc:mysql://localhost:3306/数据库名,要注意指定时区参数,否则驱动会报错。第二是 MySQL 依赖没有加或者版本不对。第三是 MySQL 服务本身没有启动。
如果看到的报错里带Public Key Retrieval is not allowed,一般是在 URL 上加上allowPublicKeyRetrieval=true参数。如果看到时区报错,最常见的解决方案是:
spring: datasource: url: jdbc:mysql://localhost:3306/todo_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: updateddl-auto的值值得解释一下:update表示启动时根据实体类自动更新表结构,开发阶段很方便;生产环境一般不会用,因为自动改表结构风险很大。小白阶段用update没问题,但你要知道它和create、validate的区别,不然将来部署到服务器上会遇到数据被清空的情况。
6.4 热部署与调试技巧
反复重启项目真的很消磨耐心。Spring Boot 官方提供了一个热部署工具 devtools,加一个依赖就能明显改善开发体验:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>加了依赖以后,只要你重新编译代码(IDEA 里一般是自动编译,或者按快捷键手动触发),Spring Boot 会自动重启应用。注意,它改的是类路径下的代码,改配置文件同样生效。这一步对我来说是开发效率的分水岭,没有它之前,我每次改一行 Controller 代码,都要经历“停止 → 等待 → 启动”的漫长循环。
调试方面,新手往往依赖打印日志排查问题,但遇上复杂逻辑,打印效率太低。我建议你花点时间学会 IDE 的断点调试:在代码行号旁边点一下,启动项目时用 Debug 模式,程序执行到这一行就会停下来,你可以慢慢查看每个变量的值,然后单步跟踪。这个技能早学会早受益。
写在最后的小建议
这套流程走完,你的收获不是“学会了几个注解”,而是亲身体验了一条完整的全栈开发链路:从环境搭建到项目生成,从编写接口到操作数据库,再到写一个能交互的前端页面。这条链路的价值在于,它把碎片化的知识串成了体系,以后再学 Spring Security、Redis、消息队列,你会知道它们分别插在这条链路的什么位置。
根据我带过不少新人的经验,最大障碍往往不是技术难度,而是信息过载带来的焦虑。今天看到某个框架很火想学,明天看到某种架构很流行又想追,最后一事无成。先把这个最小项目吃透,比什么都强。后续你完全可以在此基础上扩展:把 H2 换成 MySQL,给待办事项加上完成状态的切换,用 Spring Security 加上登录功能,甚至部署到云服务器上让朋友访问。
第一次学习 Spring Boot 时,我最庆幸的是有人告诉我“先跑起来再说”。希望你也能扔掉“我还没准备好”的包袱,动手创建你的第一个 Spring Boot 项目。遇到报错不要慌,报错信息本身就是最好的老师。