Servlet+JSP水果超市项目:JavaWeb完整CRUD实战
2026/9/17 1:45:19 网站建设 项目流程

简介:一份基于Servlet与JSP技术实现的水果超市后台管理项目,主要面向Java Web初学者,以模拟水果超市的商品管理为场景,完整覆盖添加、删除、修改、查询等基础操作,并扩展了用户注册登录功能,适合用来串联HTTP请求处理、Servlet业务逻辑、页面跳转和数据库增删改查的完整Web开发流程。资源包共85个文件,压缩后大小约2.1MB,核心内容包括Java源文件、JSP动态页面、XML部署配置、前端CSS与JavaScript脚本、图片素材,另外还包含已编译的class文件与IDEA工程配置文件,导入开发工具即可查看项目结构。项目按标准Web工程组织目录,src保存Java类,web目录集中存放JSP与静态资源,out目录单独保存编译产物,模块划分明确。搭配Tomcat服务器运行后,可用IDEA打开并配合Navicat管理数据库,适合作为课程设计或Servlet/JSP阶段练手的参考项目。目前已有1288人学习,深入阅读其中的Servlet跳转逻辑、注册登录实现和数据库访问代码,能够较快建立Java Web开发的整体认知。

1. Servlet + JSP 的水果超市:一个能跑通的 JavaWeb 最小闭环

FruitWeb 这套水果超市 JavaWeb 项目,解压后不是一堆半成品类文件,而是一个闭合的 Servlet + JSP 应用:index.jsp打开商品列表,add.jspupdate.jsp做表单录入,register.jsp处理注册,后端由src/cn/cst下的 Servlet 和工具类撑起增删改查。它没有 Spring、没有 MyBatis,连 JSTL 都可能没引全,但这恰恰是它的价值——你可以完整看到一次 HTTP 请求如何穿过 Tomcat、Servlet、JDBC 落到 MySQL 表,中间没有框架遮住任何一步。适合刚学完 Servlet 语法想找完整实战的人,也适合做课程设计时需要一个非框架型演示系统的读者。整个运行链路直白,几乎不存在黑盒。

2. 从 FruitWeb.iml 到 web.xml:先看懂这个 JavaWeb 项目的目录语言

2.1 解压后的文件到底在说什么

拿到FruitWeb.zip,第一件事别急着导入 IDEA,先对照文件结构确认它是不是你预期的那种 JavaWeb 工程。解压后能看到以下关键对象:

路径作用
FruitWeb.imlIDEA 的模块描述文件,记录源码目录、依赖和编译输出位置
.idea/IDEA 的工作区配置,包含misc.xmlmodules.xmlworkspace.xml
src/cn/cst/Java 源码根,Servlet 类、DAO、实体类都在这里
web/Web 应用根目录,JSP、CSS、JS、图片全在web
web/WEB-INF/web.xml部署描述符,Servlet 的注册和映射从这里读
out/artifacts/FruitWeb_war_explodedIDEA 的编译输出目录,等价于解压后的 WAR 包

FruitWeb.iml.idea是给人看的调试信息,真正决定程序行为的是srcweb两个目录的配合。web下的admin.css.bak这类备份文件说明原作者在改样式时保留了工作痕迹,对学习者反而是个参考点。

2.2 web.xml 里配置了什么

对于一个不用注解的 Servlet 项目,web.xml是入口。打开web/WEB-INF/web.xml,核心配置通常长这样:

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <display-name>FruitWeb</display-name> <servlet> <servlet-name>FruitServlet</servlet-name> <servlet-class>cn.cst.servlet.FruitServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>FruitServlet</servlet-name> <url-pattern>/fruit</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>

这段配置的逻辑很直接:servlet-class告诉容器去哪个类找处理逻辑,url-pattern把浏览器访问路径绑定到该类。<welcome-file-list>决定访问根路径时默认打开index.jsp。这里有几个初学者常犯的错:url-pattern忘记写前导斜杠,或者映射路径和 JSP 表单里的action对不上,结果表单提交直接 404。遇到这种情况优先去web.xml查映射,别急着看 Java 代码。

2.3 Tomcat 怎么把这套目录变成可访问的 Web 应用

Tomcat 对 JavaWeb 应用的部署方式有三种:直接放置 WAR 包、放置解压目录、在server.xml里配置 Context。本项目在 IDEA 里跑时用的是out/artifacts/FruitWeb_war_exploded,这个目录内部结构已经和web/是镜像关系,Tomcat 把它当作 docBase,读取WEB-INF/web.xml完成 Servlet 注册和映射。WEB-INF下的classes存放编译后的.classlib存放 JDBC 驱动等第三方 JAR,这个目录对浏览器不可见,是容器和业务代码的隔离边界。

3. 水果 CRUD 的核心:Servlet 接管 HTTP 请求后的数据流

3.1 数据库侧:水果表该怎么设计

Servlet 只是中间层,数据最终落在 MySQL。演示项目一般会建一张水果表和一张用户表,表设计直接决定 DAO 层代码的复杂度。水果表常见设计是:

CREATE TABLE fruit ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', name VARCHAR(50) NOT NULL COMMENT '水果名称', price DECIMAL(10,2) NOT NULL COMMENT '单价,保留两位小数', stock INT NOT NULL DEFAULT 0 COMMENT '库存数量', unit VARCHAR(10) DEFAULT '斤' COMMENT '计量单位', description VARCHAR(255) COMMENT '备注描述' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有个容易被忽略的细节:价格字段用DECIMAL(10,2)而不是FLOATFLOAT2.99这类小数时,二进制浮点会引入精度误差,做金额比较或求和会出现预期外的结果。DECIMAL在 MySQL 内部按字符串存储,精度是精确的。实测中,用FLOAT存几万条数据后做SUM,结果经常差出几分钱,对水果超市这种单价不高但流水高频的场景,账面对不上才是大坑。另外字符集选utf8mb4,避免特殊字符和 Emoji 写入时报Incorrect string value

3.2 Servlet 里怎么组织增删改查

这个项目的 Servlet 组织方式很典型——一个FruitServlet通过action参数分发到不同处理方法,相当于手动实现前端控制器。核心骨架如下:

@WebServlet("/fruit") public class FruitServlet extends HttpServlet { private FruitDao fruitDao = new FruitDao(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter("action"); if ("delete".equals(action)) { int id = Integer.parseInt(req.getParameter("id")); fruitDao.deleteById(id); resp.sendRedirect(req.getContextPath() + "/fruit?action=list"); } else if ("edit".equals(action)) { int id = Integer.parseInt(req.getParameter("id")); req.setAttribute("fruit", fruitDao.findById(id)); req.getRequestDispatcher("/update.jsp").forward(req, resp); } else { req.setAttribute("fruitList", fruitDao.findAll()); req.getRequestDispatcher("/list.jsp").forward(req, resp); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String action = req.getParameter("action"); if ("add".equals(action)) { // 读取表单字段并调用 dao.insert } else if ("update".equals(action)) { // 读取表单字段并调用 dao.update } resp.sendRedirect(req.getContextPath() + "/fruit?action=list"); } }

代码逻辑说明:doGet里处理所有幂等操作——删除、回显编辑页、查询列表;doPost处理写操作——新增和修改。这样区分的原因是实现 REST 语义的简化版:GET 不应该产生数据变更,POST 才是提交数据的入口。resp.sendRedirect在写操作后重定向到列表页,避免用户刷新浏览器时重复提交表单,这是 Post-Redirect-Get 模式的简化版,对上课演示够了。参数方面有个注意点:Integer.parseInt解析id时如果参数不是数字会抛NumberFormatException,生产环境需要加 try-catch 或使用int包装类做判空,这里课堂上图省事直接 parse 是很常见的写法。

3.3 中文乱码的两个关键位置

JavaWeb 中文乱码几乎集中在两个位置:请求参数乱码和响应输出乱码。项目里doPost开头那句req.setCharacterEncoding("UTF-8")能解决表单 POST 提交的中文,但解决不了 GET 请求 URL 里的中文参数。Tomcat 7 及以下默认URIEncodingISO-8859-1,必须到conf/server.xml改连接器配置:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />

Tomcat 8 及以上默认就是 UTF-8,改动可省略。响应侧的乱码则在每个 Servlet 输出前设置:

resp.setContentType("text/html; charset=UTF-8");

这个设置要和 JSP 头部的pageEncoding="UTF-8"一致,否则 Servlet 转发到 JSP 后,JSP 以错误编码渲染页面,中文仍然会花屏。排查乱码时按「请求编码 → 响应编码 → 数据库字符集」三处逐一确认,见效最快。

4. 从 index.jsp 到 updatepw.jsp:前端表单和后端 Servlet 的对接

4.1 list.jsp 怎么把数据库里的水果渲染成表格

列表页是用户第一眼看到的东西,也是 DAO 查询结果的展示层。传统 JSP 渲染有两种选择:直接用<% %>脚本片段四处输出,或者用 JSTL 标签保持页面整洁。这个项目里list.jsp的写法偏向脚本片段风格:

<% List<Fruit> fruitList = (List<Fruit>) request.getAttribute("fruitList"); if (fruitList != null && !fruitList.isEmpty()) { for (Fruit f : fruitList) { %> <tr> <td><%= f.getId() %></td> <td><%= f.getName() %></td> <td>¥<%= f.getPrice() %></td> <td><%= f.getStock() %></td> <td> <a href="fruit?action=edit&id=<%= f.getId() %>">修改</a> <a href="fruit?action=delete&id=<%= f.getId() %>" onclick="return confirm('确定删除该水果吗?')">删除</a> </td> </tr> <% } } %>

用脚本片段写循环的好处是直观,任何学过 Java 语法的人都能对出来;坏处是页面里混合了 Java 代码,维护时会看到out.println和 HTML 标签交错。再深一层,<%= f.getPrice() %>直接输出浮点数,如果表中priceFLOAT类型会出现1.1999999这种结果,这也是前面强调用DECIMAL的连带好处。列表页的删除链接带了个onclick="return confirm(...)",这个细节避免了误删,是很实用的界面交互成本控制。

4.2 register.jsp 的注册数据怎么落到数据库

register.jsp对应的是一段独立的注册流程,可能直接由RegistServlet处理,也可能混在用户 Servlet 中。它的表单结构典型如下:

<form action="register" method="post"> <label>用户名</label> <input type="text" name="username" required maxlength="20"> <label>密码</label> <input type="password" name="password" required minlength="6"> <label>确认密码</label> <input type="password" name="confirmPwd" required> <button type="submit">注册</button> </form>

对应 Servlet 端至少要做两件事:判空和密码一致性校验。常见的校验代码:

String username = req.getParameter("username"); String password = req.getParameter("password"); String confirmPwd = req.getParameter("confirmPwd"); if (username == null || username.trim().isEmpty()) { resp.getWriter().write("<script>alert('用户名不能为空');history.back();</script>"); return; } if (!password.equals(confirmPwd)) { resp.getWriter().write("<script>alert('两次密码不一致');history.back();</script>"); return; }

注意这段代码的密码比对用的是 Java 字符串的equals,如果直接用password == confirmPwd是比较对象引用,结果永远为 false,这是 Java 新手最常见的逻辑雷点。项目里有提示updatepw.jsp存在,说明密码修改流程也已实现,修改时通常需要校验旧密码,这一步其实就是「查询用户→比对旧密码→更新新密码」的串行操作,可以在UserDao里增加一个updatePassword方法完成。

4.3 pintuer.css:这个项目的前端框架选型

web/css目录下有pintuer.csspintuer.js,这表明项目使用了 Pintuer 这个轻量级 CSS 框架。它的 class 命名风格类似 Bootstrap,但体积更小,适合无构建工具的传统 JSP 项目。项目里常见用法:

class 名用途
container页面居中容器
table/table-bordered表格样式及边框修饰
button/bg-green按钮基础样式与背景色
form-group表单控件分组布局
text-center文本水平居中

这种框架的取舍在于:它比手写 CSS 干净,又比 Bootstrap 轻,适合教学项目里快速搭出「看得过去」的管理界面。.bak后缀文件提示开发者在调样式时保留过旧的 CSS 副本,这也是没有版本控制时代的土办法。

5. 部署与排错的关键路径:从 IDEA 启动到验证一个完整流程

5.1 IDEA 里 Artifact 和 Tomcat 没对上

导入项目后,File -> Project Structure -> Artifacts要能看到FruitWeb_war_exploded,Run 配置里 Tomcat Server 的 Deploy 页签要确认 Application context 设为/FruitWeb_war_exploded或自定义的/fruit。上下文路径和web.xml里的映射不冲突,但它决定浏览器访问的根 URL。现实中大量 404 来自这里:代码改了、Servlet 重编了,但 IDEA 没有重新 build artifact,导致部署到 Tomcat 的是旧 class。遇到「改了代码不生效」先在Build -> Rebuild Project后重启 Tomcat。

5.2 数据库连不上的排查顺序

项目WEB-INF/lib如果已经带了mysql-connector-java,驱动问题就少一半。连不上时按顺序检查:MySQL 服务是否启动、驱动类名是否匹配、URL 端口是否默认 3306、用户名密码是否硬编码在 JDBC 工具类中。这里给一个稳妥的 JDBC 连接参数参考:

String url = "jdbc:mysql://localhost:3306/fruitdb?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai"; String user = "root"; String password = "123456";

characterEncoding=UTF-8serverTimezone是排查乱码和时间报错的两个必要参数,尤其 MySQL 8.x 客户端不指定时区会直接抛CST相关异常。修改数据库连接的信息后在 Navicat 里先测一次连接,排除数据库端问题,再看 Java 代码,别两头瞎猜。

5.3 验证 CRUD 最佳路径与最后建议

完整验证这个项目的健康度,最有效的顺序是:启动 Tomcat 访问index.jsp,确认欢迎页加载;点击「新增」提交一条带中文的记录,看列表页是否正常显示且不乱码;点「修改」改价格和库存,刷新后确认更新;点「删除」确认弹窗,返回列表后确认记录消失;注册一个账号后登录,检查 Session 跳转是否正常。这一套流程走通,Servlet + JSP + DAO + MySQL 这条链路就没问题。如果某个环节断了,优先在对应 Servlet 的doGetdoPost入口打一行System.out.println输出 action 参数,确认请求是否进到了预期方法。对于一个教学项目,这比引入 Log4j、配置日志级别更直接,花的时间也更少。最后建议有兴趣的读者尝试把FruitServlet里的action分发重构为switch和独立方法,或者把脚本片段改成 JSTL——这是这个项目留给你的升级空间。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询