JavaWeb超市系统实战:Servlet+JSP+JDBC三层架构与事务安全
2026/9/16 13:31:46 网站建设 项目流程

简介:这是一套面向计算机专业本科生的JavaWeb毕业设计实战资源,专为毕设开发与Java Web全栈能力训练打造,完整覆盖超市业务全流程管理需求。资源包含68个文件,主体为19个核心Java源码、37个编译后Class文件、4个MySQL建表与初始化SQL脚本(含员工、商品、进货、销售等关键表),辅以2个系统界面JPG示意图、2个运行依赖JAR包及Eclipse项目配置文件(.project、.classpath、.prefs等),整体压缩包仅3.5MB,轻量易部署。已有796人学习下载,说明其调试成熟度与教学适配性获广泛验证。用户可直接导入Eclipse+Tomcat环境运行,获得具备双角色权限(员工/管理员)、八大功能模块(系统管理、员工/商品/货架/类型/进货/销售信息管理)的可执行系统,同时掌握JSP+Servlet+JDBC三层架构实践、MySQL数据库设计与CRUD操作、B/S系统权限控制等关键技术点。

1. 这不是“抄个源码交差”的毕业设计,而是用 JavaWeb 搭建真实超市业务闭环的实战入口

很多计算机专业学生拿到“基于JavaWeb的超市管理系统源码+数据库(毕业设计).zip”时,第一反应是解压、导入、跑通、截图、写报告——但真正拉开差距的,是从这个压缩包里识别出三层架构落地痕迹、MVC职责边界、数据库事务边界和Web层安全基线。它本质是一个轻量级但结构完整的零售业务系统:商品进销存、员工权限分级、销售单据生成、库存实时扣减、会员积分联动。不依赖Spring Boot自动装配,纯Servlet+JSP+JDBC实现,恰恰暴露了JavaWeb底层交互逻辑——比如request.getParameter()如何被恶意构造参数攻击、PreparedStatement为何必须替代字符串拼接、web.xml<filter>配置怎样拦截未登录访问。适合大三下学期动手能力中等以上的学生:既能通过调试Servlet生命周期理解HTTP请求流转,又能从goods.sql建表语句反推ER图设计逻辑,还能在Tomcat控制台日志里定位NullPointerException的真实源头。这不是玩具项目,它的库存扣减逻辑若不加事务控制,会直接导致超卖;它的登录校验若漏掉大小写敏感处理,会让测试用例失败。

2. 从压缩包到可运行环境:解压、建库、配Tomcat的三步验证法

2.1 解压后目录结构解析与关键文件定位

拿到.zip文件后,先不解压到IDE工作区,而是用命令行快速查看结构,确认是否含标准JavaWeb骨架:

unzip -l "基于Javaweb的超市管理系统源码+数据库(毕业设计).zip" | head -20

典型输出应包含:

  • src/目录(含com/supermarket/dao/,com/supermarket/service/,com/supermarket/servlet/
  • WebContent/webapp/(含WEB-INF/web.xml,index.jsp,css/,js/
  • database/sql/(含supermarket.sqlgoods.sql
  • lib/(含mysql-connector-java-5.1.47.jar8.x版本)

提示:若lib/中缺失MySQL驱动,或web.xml<servlet-class>指向不存在的类,说明该源码包已被二次修改但未清理残留引用,需优先修复类路径。

2.2 数据库初始化:从SQL脚本到可查询状态

进入database/目录,找到建库脚本(常见名:supermarket.sql),用MySQL客户端执行:

mysql -u root -p < supermarket.sql

脚本应包含三部分:

  1. CREATE DATABASE supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  2. USE supermarket;后的建表语句(重点检查goods表是否有stock INT NOT NULL DEFAULT 0字段)
  3. INSERT INTO user (username, password, role) VALUES ('admin', '21232f297a57a5a743894a0e4a801fc3', 'manager');—— 此处MD5密码对应明文admin,是登录凭证

执行后验证:

mysql -u root -p -e "USE supermarket; SELECT COUNT(*) FROM goods;"

返回非零值即建表成功。若报错Unknown database 'supermarket',说明脚本未执行建库语句,需手动创建数据库并指定字符集。

2.3 Tomcat 8.5+ 部署配置要点

该类项目通常适配Tomcat 8.5或9.0,禁止使用Tomcat 10+(因Jakarta EE命名空间变更导致javax.servlet.*包找不到)。配置步骤:

  1. 将解压后的WebContent/(或webapp/)整个目录复制到$CATALINA_HOME/webapps/supermarket/
  2. 修改$CATALINA_HOME/conf/context.xml,在<Context>内添加数据库连接池配置:
<Resource name="jdbc/supermarket" auth="Container" type="javax.sql.DataSource" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/supermarket?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai" username="root" password="your_password" maxActive="20" minIdle="5" maxWait="10000"/>
  1. $CATALINA_HOME/webapps/supermarket/WEB-INF/web.xml中确认资源引用:
<resource-ref> <description>DB Connection</description> <res-ref-name>jdbc/supermarket</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>

注意:url参数中serverTimezone=Asia/Shanghai不可省略,否则MySQL 8.0+会报The server time zone value '...' is unrecognized错误;&在XML中必须转义为&amp;

3. 核心业务模块代码走读:从登录校验到库存扣减的链路拆解

3.1 登录Servlet中的安全边界实践

打开src/com/supermarket/servlet/LoginServlet.java,关键逻辑在doPost()方法:

String username = request.getParameter("username").trim(); String password = request.getParameter("password"); // 1. 空值校验 if (username.isEmpty() || password == null) { request.setAttribute("msg", "用户名或密码不能为空"); request.getRequestDispatcher("login.jsp").forward(request, response); return; } // 2. 密码MD5加密(注意:此处为演示,生产环境应加盐) String md5Password = DigestUtils.md5Hex(password); // 3. DAO层查询(使用PreparedStatement防注入) User user = userDao.findByUsernameAndPassword(username, md5Password); if (user != null) { // 4. Session绑定用户信息 HttpSession session = request.getSession(); session.setAttribute("user", user); session.setMaxInactiveInterval(1800); // 30分钟无操作失效 response.sendRedirect("main.jsp"); } else { request.setAttribute("msg", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); }
关键参数说明:
  • DigestUtils.md5Hex()来自Apache Commons Codec,需确认lib/commons-codec-1.11.jar存在
  • session.setMaxInactiveInterval(1800)设置超时时间单位为秒,避免长期占用服务器内存
  • request.getRequestDispatcher().forward()实现服务端跳转,URL地址栏不变,适合表单提交后重定向前的数据传递

3.2 销售单生成中的事务控制实现

src/com/supermarket/servlet/SaleServlet.javadoPost()方法调用saleService.createSale(sale),其核心在SaleServiceImpl.java

public boolean createSale(Sale sale) { Connection conn = null; try { conn = JdbcUtils.getConnection(); // 从连接池获取 conn.setAutoCommit(false); // 关闭自动提交 // 1. 插入销售主表 saleDao.insert(conn, sale); // 2. 扣减库存(关键:需逐条更新goods表stock字段) for (SaleItem item : sale.getItems()) { goodsDao.updateStock(conn, item.getGoodsId(), -item.getQuantity()); } conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { if (conn != null) { try { conn.rollback(); // 任一环节失败回滚 } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { JdbcUtils.closeConnection(conn); } }
事务参数设计逻辑:
  • conn.setAutoCommit(false)是手动事务起点,避免单条SQL成功后无法回滚
  • goodsDao.updateStock()执行UPDATE goods SET stock = stock - ? WHERE id = ?,用stock = stock - ?而非stock = ?防止并发超卖
  • JdbcUtils.closeConnection(conn)确保连接归还池,否则连接泄漏会导致后续请求卡死

3.3 商品管理页面的分页与模糊查询实现

src/com/supermarket/servlet/GoodsListServlet.java中分页逻辑:

int currentPage = Integer.parseInt(request.getParameter("page")); int pageSize = 10; String keyword = request.getParameter("keyword"); // 构造动态SQL(MyBatis风格,但此处为JDBC原生) String sql = "SELECT * FROM goods WHERE 1=1"; List<Object> params = new ArrayList<>(); if (keyword != null && !keyword.trim().isEmpty()) { sql += " AND name LIKE ?"; params.add("%" + keyword.trim() + "%"); } sql += " LIMIT ?, ?"; params.add((currentPage - 1) * pageSize); params.add(pageSize); List<Goods> goodsList = goodsDao.findList(sql, params.toArray()); // 总记录数查询(独立SQL,避免子查询性能问题) String countSql = "SELECT COUNT(*) FROM goods WHERE 1=1"; if (keyword != null && !keyword.trim().isEmpty()) { countSql += " AND name LIKE ?"; } int totalCount = goodsDao.getCount(countSql, keyword != null ? new Object[]{"%" + keyword.trim() + "%"} : new Object[]{});
分页参数优化点:
  • LIMIT ?, ?中第一个?为偏移量(currentPage-1)*pageSize,第二个为每页条数,避免OFFSET过大导致性能下降
  • name LIKE ?参数化防止SQL注入,%由Java代码拼接而非SQL中硬编码
  • 总数查询与列表查询分离,避免SELECT COUNT(*) OVER()等窗口函数兼容性问题

4. 常见运行故障定位与修复:从404到500的精准排查路径

4.1 HTTP 404错误:资源路径与Servlet映射匹配检查表

现象检查点修复命令/操作
访问http://localhost:8080/supermarket/login.jsp报404确认webapps/supermarket/目录存在且含login.jspls $CATALINA_HOME/webapps/supermarket/login.jsp
访问http://localhost:8080/supermarket/login(无后缀)报404检查web.xml<servlet-mapping>路径是否匹配<url-pattern>/login</url-pattern>对应<servlet-name>LoginServlet</servlet-name>
JSP中<jsp:include page="/header.jsp"/>报404确认header.jspwebapps/supermarket/根目录而非WEB-INF/mv webapps/supermarket/WEB-INF/header.jsp webapps/supermarket/

提示:Tomcat启动日志中出现INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory即部署成功,若无此日志说明war包未解压或目录名不合法。

4.2 HTTP 500错误:数据库连接与空指针的堆栈溯源

当点击“登录”按钮出现500错误,查看$CATALINA_HOME/logs/catalina.out末尾:

Caused by: java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)

→ 检查context.xmlusername/password是否与MySQL实际账户一致

Caused by: java.lang.NullPointerException at com.supermarket.dao.UserDao.findByUsernameAndPassword(UserDao.java:42)

→ 定位UserDao.java第42行,常见为ResultSet rs = stmt.executeQuery(sql);后未判空直接rs.next(),需改为:

if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); // ...赋值 return user; } return null; // 必须返回null,否则上层NPE

4.3 中文乱码问题:三处字符集强制统一方案

层级配置位置强制参数
数据库连接context.xmlurl参数?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
JSP页面login.jsp顶部<%@ page contentType="text/html;charset=UTF-8" %>
Servlet响应LoginServlet.javadoPost()开头response.setContentType("text/html;charset=UTF-8");

若仍乱码,在web.xml中添加过滤器:

<filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

5. 毕业设计答辩必答:三个能体现工程思维的深度问题与应答逻辑

5.1 “为什么不用Spring Boot而坚持Servlet+JSP?”

应答逻辑:突出技术选型与教学目标的匹配性
“本项目定位为JavaWeb基础能力验证,Servlet生命周期(init/service/destroy)能直观展示HTTP请求处理全流程,JSP内置对象(request/session/application)帮助理解作用域差异。Spring Boot的自动配置虽提升开发效率,但会掩盖web.xml部署描述符、ServletContext初始化、Filter链执行顺序等核心概念。例如在LoginFilter中,我们手动判断session.getAttribute("user")==null并重定向,这比Spring Security的@PreAuthorize注解更能体现权限校验的本质。”

5.2 “库存扣减如何保证高并发下的数据一致性?”

应答逻辑:区分当前实现与工业级方案的演进路径
“当前版本通过数据库事务+UPDATE goods SET stock = stock - ?实现基础一致性,但存在‘查-改’分离导致的ABA问题。答辩时可提出升级路径:1)引入Redis分布式锁(SET goods:1001 lock NX PX 10000)控制单商品并发;2)将库存扣减下沉为消息队列异步任务,主流程只生成销售单,由消费者服务最终更新库存;3)采用MySQL行锁+乐观锁(version字段),在UPDATE语句中加入AND version = ?条件。”

5.3 “如何证明你的系统满足数据库课程设计的范式要求?”

应答逻辑:用具体字段和关系反推范式合规性
“以goods表为例:id为主键,namepricecategory_id均完全依赖id(满足1NF/2NF);category_id外键关联category表,避免品类名称重复存储(消除冗余,满足3NF)。反例是若将category_name直接存入goods表,则违反3NF。我绘制的ER图中,salesale_item为一对多,sale_itemgoods_id外键但不含goods_name,正是3NF的实践体现。”

提示:答辩时携带打印版ER图(用PowerDesigner或draw.io绘制),标注主键、外键及联系基数,比口头描述更直观可信。

5.4 毕业设计代码优化的三个立即生效技巧

技巧操作位置效果
JSP表达式语言(EL)替换Java脚本<% out.print(user.getUsername()); %>改为${user.username}消除Scriptlet,提升可维护性,避免XSS风险(EL默认HTML转义)
DAO层统一异常处理BaseDao.java中封装try-catch SQLException,抛出自定义DaoException上层Service无需处理JDBC特有异常,符合分层解耦原则
静态资源分离部署css/js/images/移至Nginx静态服务目录,Tomcat仅处理动态请求减少Tomcat线程压力,提升CSS/JS加载速度,符合生产环境部署规范

webapps/supermarket/css/目录复制到/usr/share/nginx/html/supermarket/css/,并在JSP中将<link href="css/style.css">改为<link href="http://static.example.com/supermarket/css/style.css">,即可完成动静分离。

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

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

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

立即咨询