1. 这个项目到底在做什么
如果你也是按部就班学过 Servlet、JSP、Filter、Listener,又跟着视频敲过几个“员工管理系统”的练习,那你大概率会有一种感觉:单看每个知识点都懂,但一说到“完整跑通一个 JavaWeb 项目”,总是差点意思。这里说的 JavaWeb_05,就是这种“差点意思”被补齐的阶段——从零开始,用 IDEA 完整搭建一个基于 Spring Boot + MySQL 的 JavaWeb 实战项目,覆盖从数据库建表、后端接口开发、前端页面联调到部署验证的全流程。
用大白话讲,这个阶段做的事情就是:把你脑子里的散装知识点串成一条完整的线。比如 Session 到底怎么用于登录保持、从前端 Form 表单提交到后端 Controller 接收参数中间发生了什么、MyBatis 的 Mapper 接口为什么不能直接 new 却能被注入调用——这些问题,在你只是看笔记、抄代码的时候是感觉不到的,只有真正动手做一个包含登录、查询、分页、增删改查的完整案例,才会被逼着去弄明白。
这篇内容适合两类人看:一类是正在跟着课程或笔记走、刚学到 JavaWeb 框架整合阶段的学生;另一类是已经工作但想快速把“CS 架构思想”落实到 Spring Boot 工程里的转行新人。我尽量把每一个步骤背后的“为什么”也讲清楚,而不是单纯甩一段能跑通的代码。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体设计与技术选型思路
2.1 为什么第五阶段通常落在 Spring Boot 而不是继续堆 Servlet
在 JavaWeb 的学习曲线里,第一阶段是 HTML + Servlet + Tomcat 手工配置,第二阶段是 JSP + JSTL 做动态页面,第三阶段是 Filter + Listener 做权限和监听,第四阶段是 MySQL + JDBC + 连接池,到了第五阶段,基本上就该引入 Spring Boot 了。这样安排的逻辑是:前四个阶段把“底层机制”铺完,你会手动写过 web.xml、自己封装过 JDBC 工具类、手动处理过乱码过滤器——这时候你才知道框架到底帮你省了什么。
Spring Boot 在这个阶段的定位,不是一个“新知识”,而是一个“效率工具”。它把 Tomcat 内嵌、依赖版本管理、自动配置、组件扫描这些本来需要手动折腾的事情全部接管了。你只需要关注自己的业务代码,其他的让框架去做。
我见过不少人跳过前四阶段直接学 Spring Boot,结果就是:能跑通 Demo,但一遇到复杂点的问题(比如为什么 Controller 里注入 Service 不报错、为什么修改了静态资源需要重启或热部署)就彻底懵了。JavaWeb_05 的价值恰恰在于它把前面的基础积累和框架能力衔接起来了。
2.2 技术选型的常见组合与取舍
这个阶段的完整案例,最稳妥的技术组合我总结下来就是一套“黄金搭配”:Spring Boot 2.x + MyBatis + MySQL 5.7/8.0 + Thymeleaf(或前后端分离式的 Ajax 接口)。这里有几个选型理由值得说破:
- Spring Boot 2.x 而不是 3.x:很多课程和笔记都停留在 2.x,原因是 3.x 基于 Jakarta EE,包名变更(javax 改 jakarta),不少传统教学资源和旧项目代码不兼容。如果你是自己做练习,建议先跟着主流资料走 2.x,等基础打牢了再升级。
- MyBatis 而不是 Spring Data JPA:虽然 JPA 在真实企业里也常见,但对于学习阶段,MyBatis 的 SQL 可控性更强,你能直观看到 SQL 和方法的映射关系,排查问题也 easier。而且国内大部分 JavaWeb 教学案例都以 MyBatis 为主,配套资料多。
- Thymeleaf 还是前后端分离:如果你是想快速看见页面效果,Thymeleaf 服务端渲染更直接;如果你已经会一点 JS,也可以做成 JSON 接口 + 静态页面。二者不冲突,第五阶段建议先用 Thymeleaf 跑通整体流程,再考虑拆成前后端分离。
2.3 工程目录结构的设计密码
很多人建完 Spring Boot 工程,图省事把 Controller、Service、Mapper 全塞在启动类同级的包里,跑是能跑,但一旦功能变多就乱成一锅粥。第五阶段一定要从开始就建立分层思想:
text复制com.example.demo
├── controller(接收请求、参数校验、返回结果)
├── service(业务逻辑、事务控制)
│ └── impl(Service 实现类)
├── mapper(MyBatis 持久层接口)
├── entity(数据库表对应的实体类)
├── dto(数据传输对象,前端和后端交互的载体)
├── config(配置类,如登录拦截器、跨域配置)
└── common(统一返回值、异常处理、工具类)
这个分层的逻辑不是瞎分的。每一层都有自己的职责边界:Controller 只负责协议转换,不写业务逻辑;Service 只负责业务规则,不出现 SQL;Mapper 只负责数据访问,不参与业务判断。这样分完之后,调试的时候你能迅速定位问题出在 HTTP 层、业务层还是数据层,而不是从头翻到尾找一处逻辑埋在哪个犄角旮旯。
3. 核心功能拆解与实现细节
3.1 登录功能不是“查一次表”那么简单
登录是 JavaWeb 项目的永恒主题,但实际做的时候你会发现它牵扯出一串问题:密码怎么存、登录状态放哪、哪些页面需要拦截、用户退出怎么失效。第五阶段的完整案例里,我最建议把登录做成“表单提交 + Session 保存 + 拦截器校验”三层结构。
后端接收用户名的密码之后,要做两件事:第一,参数合法性校验,空值直接在 Controller 层就拦掉,不用打到数据库;第二,真正查询用户表之前,密码需要加密比对。这里很多人会踩坑:数据库中的密码字段用明文存储,一旦数据库泄露,所有用户账号全裸奔。至少也得用 MD5 加盐处理,更进一步用 BCrypt。
至于登录状态的保持,代码实现上就是登录成功后把用户信息(一般是 userId + 用户名)塞进 Session,然后在 Spring Boot 里自定义一个 HandlerInterceptor,在 preHandle 方法里检查 Session 是否存在指定 key,没有就直接重定向到登录页。这一步把当年你在原生 Servlet 里手动写的过滤器逻辑升级成了框架风格的拦截器,本质是同一个套路,但表达方式更优雅。
3.2 分页查询的前后端完整链路
分页在 JavaWeb 里是一个“看起来简单、做起来琐碎”的功能。它的核心本质是:前端传两个参数(当前页码 pageNum、每页条数 pageSize),后端按这两个参数拼接 LIMIT 语句,查出当前页的数据,同时还要返回一个总条数 total 给前端用来渲染分页按钮。
用 MyBatis 实现分页有两种思路。一种是手动处理,在 Mapper.xml 里写:
xml复制<select id="selectUserPage" resultType="com.example.demo.entity.User">
SELECT * FROM user
<where>
<if test="keyword != null and keyword != ''">
AND username LIKE CONCAT('%', #{keyword}, '%')
</if>
</where>
ORDER BY create_time DESC
LIMIT #{pageNum}, #{pageSize}
</select>
注意这里 LIMIT 的第一个参数如果前端传的是“第 1 页”,那你在后端需要换算成 (pageNum - 1) * pageSize,这个偏移量计算是分页最常见的 bug 来源。另一种做法是引入 PageHelper 插件,在 Service 层方法执行前写一行 PageHelper.startPage(pageNum, pageSize),后续紧跟的第一个查询就是被分页的查询,插件自动拼 LIMIT 并自动查 count。这在小型项目里非常香,省掉了大量手写 count 查询的重复劳动。
单纯从学习角度,我建议两种方式都试一遍。先手写一次,感受 LIMIT 和 count 的配合方式;再用 PageHelper,体会插件帮你在底层做了什么。两相对照,你对“框架封装”的认知会更清晰。
3.3 增删改查中容易被忽略的事务控制
第五阶段的案例里肯定绕不开增删改查,但很多人只顾着写出 insert、update、delete 的 SQL,却忘了事务这回事。单个 SQL 操作数据库默认是自动提交的——也就是说一条 insert 成功就成功了,失败就失败了。但真实业务里一次操作往往涉及多条 SQL,比如删除一个用户,你要先删除他的角色关联表,再删除用户主表记录。此时如果第一条 SQL 成功、第二条失败,数据就处于“半删除”状态,这是绝对不能容忍的。
在 Spring Boot 里控制事务非常简单:在 Service 实现类的方法上标注 @Transactional 注解即可。这个注解会告诉 Spring 容器:这个方法里所有的数据库操作要在同一个事务里执行,任何一步抛出 RuntimeException 都会触发整体回滚。但这里有两个细节容易被坑到:
一是 @Transactional 默认只回滚 RuntimeException 和 Error,如果你在方法里手动 catch 了异常并且没有重新抛出,事务不会回滚。二是自调用问题——同一个类里的方法 A 调用方法 B,B 上有 @Transactional,这个注解其实不会生效,因为 Spring AOP 代理调用的是外部代理对象,内部自调用绕过了代理。实际开发中建议把事务注解放在独立的 Service 实现类方法上,避免内部调用导致事务失效。
3.4 统一返回结果与全局异常处理的价值
在纯 JSP 时代,后端返回的是一个完整的页面,前后端不分离,返回格式这事还不那么明显。一旦进入接口开发模式(哪怕是 Thymeleaf + Ajax 混合),统一的返回结构就变得极其重要。别让 Controller 有时候返回字符串、有时候返回 Map、有时候又直接返回实体对象,前端联调时会想打人。
我习惯在项目中定义一个通用的返回类,结构大致如下:
java复制public class Result<T> {
private Integer code; // 200 成功,500 失败
private String message; // 提示信息
private T data; // 响应数据
}
Controller 里所有的接口都返回这个结构,成功的用 Result.success(data),失败的用 Result.error("错误描述")。这套统一结构解决了一个核心问题:前端只需要写一套解析逻辑,就能处理所有接口的响应。状态码 200 和 500 的判断很简单,如果有业务上的区别(比如未登录 401、参数错误 400),也可以在 code 里扩展。
全局异常处理则是把 Controller 里到处 try-catch 的坏味道收拢起来。用 @RestControllerAdvice 定义一个全局异常处理器,分别处理业务异常、参数校验异常、系统异常,这样 Controller 里就只剩下干净的调用逻辑,出错时也能返回友好提示而不是一堆堆栈信息。这一步是很多教学案例不会强调的,但真实项目里这就是标配能力。
4. 环境搭建与运行配置实操
4.1 IDEA 配置 JavaWeb 项目的几个关键位置
JavaWeb_05 阶段最痛苦的事情之一,是 IDEA 里相关配置不熟悉导致项目跑不起来。很多人明明代码没问题,却在环境层面卡了一两个小时,气到怀疑人生。这里把关键配置点按顺序列出来,照着检查一遍基本能排查掉 90% 的运行问题。
- JDK 与 Project SDK 一致:File -> Project Structure -> Project,确认 Project SDK 选择的是你安装的 JDK 版本,Java 版本也保持一致。
- Maven 配置:Settings -> Build Tools -> Maven,确认 Maven home path 指向你本地的 Maven(而不是 IDEA 内置),User settings file 配好 settings.xml,Local repository 路径别带中文。
- 编码统一 UTF-8:Settings -> Editor -> File Encodings,把 Global Encoding、Project Encoding、Properties Files 全部设为 UTF-8。这里偷懒不设,后面乱码问题能让你怀疑人生。
- Spring Boot 的启动方式:不需要配置 Tomcat!直接在启动类上右键 Run,内嵌 Tomcat 会自己起来。很多人刚从原生 Servlet 转过来,还在傻傻找 Tomcat 配置,其实时代已经变了。
4.2 数据库初始化与配置的实操细节
第五阶段的完整案例一般需要先建库建表。我强烈建议你在项目的 resources 目录下放一个 db.sql 初始化脚本,内容包含建库、建表、插入测试数据。这个习惯有好处:任何人拿到你的项目源码,只需要执行一遍脚本就能把数据库环境拉起来,而不是靠口头解释表结构。
数据库连接配置写在 application.yml 或 application.properties 里,给出一个可直接参考的模板:
yaml复制spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/javaweb05?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: 123456
thymeleaf:
cache: false
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.demo.entity
这里有几个参数千万别乱动,动了就有血泪教训。serverTimezone 一定要设置成 Asia/Shanghai,否则高版本 MySQL 驱动连接时会报时区错误。characterEncoding=utf8 保证数据库连接层面的中文不乱码。useSSL=false 是为了在本地开发时避免 SSL 证书提示干扰。thymeleaf.cache 开发阶段务必要关掉,否则你改完 HTML 页面要重启整个项目才能看到效果,关掉之后刷新页面就能生效。
4.3 启动失败的常见报错与应急解法
按照经验,第一次把 Spring Boot 项目跑起来,大概率会遇到几个固定套路的报错。这里直接把排查路径列出来,遇到就意味着照方抓药。
- 端口被占用:报错信息通常类似
Port 8080 was already in use。解决办法要么改端口,在 application.yml 里加server.port: 8081,要么在终端用指令找到占用进程并结束它。 - 数据库连接失败:
Access denied for user 'root'@'localhost'这类报错,八成是密码不对或权限不对。先确认 application.yml 里的账号密码和本地 MySQL 一致,如果密码有特殊字符比如@、#,需要加引号或转义,否则 YAML 解析会出问题。 - Table 'xxx' doesn't exist:这说明表没建成功或你连的库不对。检查一下 URL 里的库名是否和
db.sql里创建的一致,然后确认是否真的执行过初始化脚本。 - MyBatis 绑定异常:提示
Invalid bound statement (not found),一般就是 Mapper 接口和 XML 文件没有正确匹配。检查 XML 文件是否放在resources/mapper目录下,mapper-locations 配置是否匹配,XML 里的 namespace 是否等于接口全限定名。
5. 项目运行验证与功能测试记录
5.1 从启动到登录页面的完整测试路径
当项目能够正常启动之后,别急着高兴,完整的测试路径才是检验项目“到底能不能用”的关键。我的习惯是按下面的顺序走一遍:
第一步,启动启动类,观察控制台日志。出现 Started Application in X.XX seconds 表示 Spring 容器启动成功。如果出现红色报错,优先看最底部的异常原因,前面刷屏的堆栈信息大多是干草。
第二步,浏览器访问 http://localhost:8080/,看是否能跳转到登录页面。如果页面渲染出来但样式丢失,检查静态资源路径和 Thymeleaf 的语法是否写对。如果 404,排查拦截器是不是把放行路径搞错了。
第三步,在数据库插入一个测试用户,然后用前端页面进行登录。登录成功后手动访问一个受保护的页面,确认能正常访问;退出登录后,再直接输入该页面的 URL,确认会被拦截器踢回登录页。这一步是验证登录拦截逻辑是否真正生效的关键。
第四步,测试新增、编辑、删除业务流程,尤其注意删除操作是否出现“删了主表但关联表没删干净”的问题。有事务控制的话,正常不会有问题,如果出现异常建议回头检查 @Transactional 的放置位置。
5.2 前端调试与接口排查的实战方法
在第五阶段的项目里,前端页面和后端的交互会变得复杂起来。特别是用 Ajax 请求接口时,一旦出现“后端明明返回了数据,但页面上显示不对”的情况,十有八九是数据结构对不上。遇到这种问题,我会开浏览器 F12,切到 Network 面板,直接查看请求和响应内容。这一步其实是最直接的排查方式——看请求是否发出去了、返回的是什么状态码、响应体结构是什么样的。
如果是 Thymeleaf 页面,则要留意 th:each 循环遍历时字段名的匹配情况。实体类中的 userId 字段在页面中默认要写成 user.userId,如果你在 HTML 里写成了 user.userid(大小写不对),很可能会得到空值。这种细节问题,IDE 不会帮你报错,只有页面显示的时候才会露馅,排查起来非常浪费时间。
5.3 日志输出与断点调试的交叉使用
遇到逻辑问题,比如条件判断不对、值没取到,我一般不靠猜,也不靠大范围打 System.out。更推荐的做法是:在关键路径上用断点调试,看看当前对象的属性值是否符合预期。比如用户登录时密码比对失败,可以在 Service 实现类的密码校验那一行断点,看看前端传过来的密码是什么,数据库查出来的密码是什么,两者分别经过什么处理。
同时把日志也利用起来。Spring Boot 内置了 SLF4J + Logback,在类里声明 private static final Logger log = LoggerFactory.getLogger(Xxx.class),然后在关键 Node
text复制if (log.isInfoEnabled()) {
log.info("用户登录成功,userId={}", userId);
}
这种带占位符的写法比字符串拼接效率更高,而且日志级别能控制输出范围。开发阶段用 debug 级别,部署阶段切 info 级别,线上排查问题再临时开 debug。统一规范之后,整个项目的可观测性会好很多,也不至于出了问题只能对着控制台干瞪眼。
6. 实际开发中绕不开的跳坑经验
6.1 编码乱码问题的完整坑位盘点
JavaWeb 项目的中文乱码问题,基本是每个初学者都要过的坎。乱码的本质是“编码-解码”前后不一致,比如 MySQL 表用了 latin1,后端用 UTF-8 写入,出来必然是乱码。排查乱码的顺序,我总结了三条链路:
- 数据库链路:MySQL 的 character_set_server、数据库字符集、表字符集、连接 URL 里的 characterEncoding 必须一致,统一 UTF-8 是最稳的。
- HTTP 链路:前端页面声明
<meta charset="UTF-8">,后端在 Spring Boot 里一般不用手动配编码过滤器了,框架默认 UTF-8。但如果你是手动写 Servlet,那就需要配置 CharacterEncodingFilter。 - 文件链路:IDEA 中代码文件的编码必须是 UTF-8,如果是 GBK 的旧文件转过来的,重新保存一次,否则编译时字符串字面量就已经是乱码。
有一件特别容易被忽略的事:Maven 的 project.build.sourceEncoding 属性没设置的话,不同平台下编译期的默认编码可能不同,Windows 下最容易中招。建议在 pom.xml 里显式声明:
xml复制<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
这一步配好,能避免很多莫名其妙的“本地好好的,同事拉下代码中文乱码”的问题。
6.2 Maven 依赖版本冲突的判断与解决
第五阶段动辄要引入十几个依赖,最常见的报错是 ClassNotFoundException 或者方法签名不匹配——这往往是传递依赖的版本冲突引起的。比如你引入了某个依赖,它内部传递了一个旧版本的第三方库,而另一个依赖需要新版本,Maven 默认仲裁规则取了较近的声明者,结果就翻车了。
处理办法是执行 mvn dependency:tree 查看依赖树,定位重复的依赖和版本,然后用 <dependencyManagement> 锁定版本,或者在具体依赖中用 <exclusions> 排除掉传递的冲突依赖。这一步对新手来说稍微有点进阶,但第五阶段的项目踩过一次之后,基本就对 Maven 的依赖机制有了深刻理解。
6.3 静态资源不生效的几类原因
在做 Thymeleaf 页面时,样式表、JS、图片这类静态资源时灵时不灵,是常见困扰。最常见的原因有三个:
一是路径不对。Spring Boot 默认静态资源目录是 classpath:/static/,你如果放在了 WEB-INF 或 templates 下,就不能通过 URL 直接访问。所以 CSS、JS、图片放在 static 目录(或其子目录)里,页面引用时使用相对路径或 /css/xxx.css 这样的绝对上下文路径。
二是拦截器拦截了静态资源。如果你自定义了 WebMvcConfigurer 做拦截器注册,但排除路径只写了 /pages/** 之类的,那 CSS 等资源就会被挡在登录拦截器外面。解决方法是把静态资源目录也加入排除列表,比如 excludePathPatterns("/css/**", "/js/**", "/images/**", "/favicon.ico")。
三是浏览器缓存。改完 CSS 或 JS 但浏览器还显示旧的,此时强制刷新(Ctrl+F5)就能看到效果。习惯上建议在项目里做版本号控制,比如 style.css?v=20250115,避免上线后因为缓存导致老资源被加载。
7. 结课后的能力检验标准
学完 JavaWeb_05 之后,怎么判断自己是不是真正掌握了这个阶段的内容?我的标准不是“能跑通示例代码”,而是看你能不能脱离参考代码独立完成下面这件事:拿到一个新的数据库设计文档,在不看任何笔记的情况下,独立搭出一个 Spring Boot + MyBatis + Thymeleaf 的完整项目,实现登录、增删改查、分页、事务处理,并解决运行过程中出现的至少三类环境或逻辑问题。
如果能做到,说明你已经具备了 JavaWeb 实战的基本素养。如果做不到,也别急着沮丧——回头对照一下是卡在哪个环节:是工程初始化不熟、是 MyBatis 映射规则没吃透、还是前端联调思路混乱?找到具体短板,针对性补,比一遍遍抄完整案例效率高得多。
从个人经验出发,我还想多说一句:JavaWeb 这条路,越往后越靠“做”而不是“看”。看十篇笔记不如亲手敲一个 Bug。最好找一个中小型题目(比如班级管理系统、图书借阅系统、个人记账本),从数据库设计开始,一直到部署上线,完整走一遍。踩过的坑、查过的报错、改过的代码,才是真正长在身上的能力。JavaWeb_05 只是这条路上的一个节点,它不是终点,但过了这个节点,你后面学 Spring Boot 整合 Redis、整合 Elasticsearch、甚至接触微服务的时候,会明显感觉地基稳了不少。
