简介:一份基于JavaWeb的超市管理系统完整源代码与数据库脚本,面向正在学习JavaWeb开发的学生、初级开发者及高校实训课程,适合作为MVC模式、Servlet、JSP与JDBC综合练习的参考项目,也是毕业设计或课程作业的实用范本。资源共150个文件,以Java源文件和编译后的class为主(33个java、66个class),包含18个JSP页面用于动态展示、19个XML配置文件、6个jar依赖库,以及SQL数据库脚本和CSS样式,压缩包整体仅5.08MB,结构紧凑,便于快速下载与本地部署。已有424人学习下载,项目覆盖用户登录、商品管理、购物车、订单处理等典型业务模块,并涉及数据库设计与事务操作,能帮助读者理解从请求处理到数据持久化的完整Web开发流程,对深入掌握JavaWeb技术栈很有助益。除了可运行的源代码和建表脚本,还提供IDE工程文件与说明文档,方便直接导入环境查看效果,也可作为实训教学和二次开发的拓展基础。
1. JavaWeb超市管理系统:这个经典课设项目到底解决什么问题
期末前两周才翻出这个 .rar,是不少人的真实状态:里面是完整的 JavaWeb 超市管理系统源代码,加一份 MySQL 数据库脚本。它是课设圈的标准模板——JSP 做页面、Servlet 接请求、JDBC 操作数据库,把商品进销存、收银台和订单串成一条能现场演示的业务线。
它解决的是:学完 JavaWeb,却不知道增删改查、Session 登录、事务这些点怎么连成一个系统。这个 .rar 给了一个最小但完整的闭环,数据库脚本导进去、部署到 Tomcat,浏览器里就能看到正在工作的管理系统。
适合谁?赶课设、想快速跑通完整项目的人;想用最短时间复习 JavaWeb 全链路、弄清三层架构真实形态的从业者。下面按我把这类项目从解压到跑通、再到改造升级的路径拆开讲。
2. 项目骨架与技术选型:JSP+Servlet+MySQL 为什么还是课设黄金组合
先别急着双击启动。解压完 .rar,第一件事是认清目录结构,搞明白每一层代码为什么存在。这套技术栈在真实企业里早被 Spring Boot 取代,但作为教学单元它还没过时——它把 HTTP 请求、Servlet 生命周期、JDBC 连接、Session 这些底层概念暴露得明明白白,Spring Boot 一个注解全包了,反而不利于理解请求是怎么进来、怎么出去的。包括黑马的 JavaWeb 笔记在内,主流教学路线都保留着 Servlet 这一站,原因就在这里。
2.1 解压后先认目录:src、WebRoot、sql 各管什么
SuperMarket/ ├── src/ │ ├── com/supermarket/entity/ # 实体类,和表结构一一对应 │ ├── com/supermarket/dao/ # JDBC 数据访问,写 SQL 的地方 │ ├── com/supermarket/service/ # 业务层,放判断逻辑和事务 │ ├── com/supermarket/servlet/ # 控制器,接收请求调 service │ ├── com/supermarket/filter/ # 过滤器,管编码和登录拦截 │ └── db.properties # 数据库连接配置 ├── WebRoot/ │ ├── admin/ # 后台 JSP 页面 │ ├── WEB-INF/ │ │ └── web.xml # 部署描述符,注册 Servlet/Filter │ └── index.jsp ├── sql/ │ └── supermarket.sql # 建库建表 + 初始数据 └── lib/ # MySQL 驱动等第三方 jar如果你解压出来的结构类似(包名、类名可能不同),说明它是标准的 JavaWeb 工程,不是把代码揉成一团的 Demo。重点确认三样东西:有没有 sql 脚本、lib 里有没有 MySQL 驱动 jar、WEB-INF 下有没有 web.xml。这三样齐全,项目大概率能救活。
为什么实体类要和表结构一一对应?因为 JDBC 查询返回的是 ResultSet,你得手动把每一列取出来塞进对象,这一层映射关系写清楚,页面才能用goods.getName()这种方式取值。而 DAO 层是整套代码里唯一允许出现 SQL 的地方——这个约定守住了,后面查问题就是一层一层往上找,而不是满项目翻字符串拼接。
2.2 三层架构职责边界:DAO 只碰 SQL,Servlet 不碰 SQL
// GoodsDao.java —— 数据访问层,职责只有一个:执行 SQL 并返回结果 public class GoodsDao { private DbUtil db = new DbUtil(); // 封装了获取连接的公共类 // 按条件分页查询商品,keyword 为空时只做分页 public List<Goods> list(String keyword, int page, int pageSize) throws SQLException { StringBuilder sql = new StringBuilder( "SELECT id, name, price, stock, category_id FROM goods WHERE 1=1"); if (keyword != null && !keyword.trim().isEmpty()) { sql.append(" AND name LIKE ?"); } sql.append(" LIMIT ?, ?"); List<Goods> list = new ArrayList<>(); // try-with-resources:连接、语句、结果集自动关闭,避免连接泄漏 try (Connection conn = db.getConnection(); PreparedStatement ps = conn.prepareStatement(sql.toString())) { int index = 1; if (keyword != null && !keyword.trim().isEmpty()) { ps.setString(index++, "%" + keyword + "%"); } // 第二页 = 跳过 (2-1)*pageSize 条,很多人这里错位 ps.setInt(index++, (page - 1) * pageSize); ps.setInt(index, pageSize); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { list.add(new Goods( rs.getInt("id"), rs.getString("name"), rs.getBigDecimal("price"), rs.getInt("stock"), rs.getInt("category_id") )); } } } return list; } }这段代码有四个可以抄走的关键点。第一,PreparedStatement 占位符传参天然防 SQL 注入,永远不要用字符串拼接的方式拼参数,这是课设代码审查必看的地方。第二,try-with-resources 自动关闭连接和结果集,很多课设被扣分不是因为功能不对,而是连接开了一堆永不关闭。第三,LIKE 模糊查询的%要自己拼在参数里,占位符只认内容不认通配符。第四,分页 LIMIT 的偏移量是(page-1)*pageSize,第一页正常第二页错位基本就是这里笔误。
Servlet 层只做三件事:收参数、调 service、决定跳哪个 JSP 还是重定向。永远别在 Servlet 里写 SQL,也别在 JSP 里用脚本片段连数据库,这是代码分层的第一条红线。service 层在这类课设里经常很薄,甚至有人直接跳过,但它的存在意义在第四节的收银事务里会体现出来:跨表操作的事务必须开在 service,不能开在某个 DAO 里。
2.3 一次请求的完整流转:从 JSP 表单到数据库再回显
以新增商品为例。JSP 页面里的表单长这样:
<form action="${pageContext.request.contextPath}/goods/add" method="post"> <input type="text" name="name" placeholder="商品名称"> <input type="number" name="price" step="0.01" placeholder="单价"> <input type="number" name="stock" placeholder="库存"> <button type="submit">保存</button> </form>注意 action 用了${pageContext.request.contextPath}动态拼接上下文路径。如果写死/supermarket/goods/add,将来部署名一改立刻 404,这是新手最容易踩的路径坑。
Servlet 收到请求后的处理:
// GoodsAddServlet.java protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); // 必须在读取任何参数之前执行 String name = req.getParameter("name"); BigDecimal price = new BigDecimal(req.getParameter("price")); int stock = Integer.parseInt(req.getParameter("stock")); Goods goods = new Goods(); goods.setName(name); goods.setPrice(price); goods.setStock(stock); GoodsService service = new GoodsService(); service.add(goods); resp.sendRedirect(req.getContextPath() + "/goods/list"); // 重定向前端再查一次 }setCharacterEncoding("UTF-8")这一行必须在读取任何参数之前调用,后端拿到的是正常中文还是乱码,在这行就定了。POST 参数的解码依赖它,而 GET 参数的解码看的是 Tomcat 的 URIEncoding,两码事,后面避坑章会细说。
整个数据流转要在脑子里形成一条链:表单里的 name 属性 → Servlet 里 getParameter 的 key → Service 组装对象 → DAO 里 PreparedStatement 的参数 → MySQL 执行 → 重定向回列表页再查一次。这条链路理解透了,后面看 SSM、Spring Boot 的项目都是同一套逻辑换了一层皮。
3. 从 .rar 到跑起来:数据库初始化与 IDEA 运行配置全流程
技术栈看懂了,接下来是让项目在你自己电脑上跑起来。我习惯的顺序是:先装环境,再导数据库,最后配 Tomcat。顺序反了会浪费大量时间在排查"代码没问题但就是起不来"的玄学问题上,其实八成是环境不匹配。
3.1 环境版本配合:JDK 8 + Tomcat 8.5 + MySQL 5.7 最稳
课设代码大多是早年按 Java 6/7 写的,用太新的环境反而麻烦。我推荐的组合如下:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 | 老代码在 JDK 8 下编译最稳,兼容性最好 |
| Tomcat | 8.5 | 和 JDK 8 配合,支持 Servlet 3.1 |
| MySQL | 5.7 | 与旧版驱动兼容,少踩认证插件和时区的坑 |
| IDEA | 2020 之后的版本 | 社区版也能配置 Tomcat,不必买旗舰版 |
如果你本机已经是 MySQL 8.0,不是不能跑,但要先处理两件事:驱动换成 mysql-connector-java 8.x,连接 URL 加serverTimezone=Asia/Shanghai,否则会报时区错误。Tomcat 10 的包名从 javax.servlet 改成了 jakarta.servlet,老课设代码直接编译不过,建议别用它。
3.2 导入 SQL 脚本:命令行和 Navicat 两条路
建库建表的脚本一般是CREATE DATABASE supermarket加一串建表语句。导入最快的方式是 MySQL 命令行:
# 脚本里已包含 CREATE DATABASE 时,直接整体导入 mysql -u root -p < sql/supermarket.sql如果脚本里只有建表语句,需要先手动建库再导入:
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARSET utf8mb4" mysql -u root -p -D supermarket < sql/supermarket.sql导入完别急着启动项目,先花 30 秒验证三件事:库在不在、表全不全、初始数据有没有。在 mysql 客户端或 Navicat 里执行:
SHOW DATABASES; -- 确认 supermarket 库存在 USE supermarket; SHOW TABLES; -- 数一数表数量对不对 SELECT * FROM admin LIMIT 5; -- 看初始账号是否导入成功这一组查询是 mysql 数据库里最常用的几条命令,30 秒做完,能省掉后面两小时"登录页永远提示密码错误"的排查。初始账号密码一般在 SQL 脚本的 INSERT 语句里,或者在 README 注释里,导完数据先查一眼记下来。
用 Navicat 的话路径是:连接本机 MySQL → 右键数据库 → 运行 SQL 文件 → 选sql/supermarket.sql→ 开始。注意如果脚本里没有USE supermarket;这句,导入前要先手动建同名库,否则表会建到默认库里,项目照样连不上。
3.3 改数据库连接配置:db.properties 是项目的命门
这类项目通常把连接参数抽在一个 properties 文件里,常见内容如下:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/supermarket?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456四个参数逐个说清楚。driver 是驱动全类名,MySQL 5.x 用com.mysql.jdbc.Driver,MySQL 8.x 要换成com.mysql.cj.jdbc.Driver。url 里的 3306 是 MySQL 默认端口,你本机改过端口必须同步改。useUnicode=true&characterEncoding=utf8控制中文字符集,这两项不写,入库的中文大概率变问号。password 要填你自己 MySQL 的密码,不是脚本里注释写的那个假密码。
改完配置先做一个最小验证:在 IDEA 里写一个三行的 main 方法,直接调一次 DbUtil 拿连接。这一步能跑通,说明数据库侧没问题,后面再报错就集中往部署侧排查,节省大量时间:
public static void main(String[] args) { Connection conn = new DbUtil().getConnection(); // 断点或打印看是否非 null System.out.println(conn != null ? "连接成功" : "连接失败"); }3.4 IDEA 配置 Tomcat:Artifacts 和 Deployment 是最容易翻车的一步
IDEA 跑 JavaWeb 项目不是点个运行按钮那么简单。它要把编译后的 classes、JSP 页面、第三方 jar 打成一个 Web 应用结构,再塞进 Tomcat 的 webapps。我的固定步骤是这样:
- 打开 Project Structure → Artifacts → 点 + → Web Application: Exploded,把 Output directory 指到你工程对应的 WebRoot 目录。
- Edit Configurations → 点 + → Tomcat Server → Local,选本地 Tomcat 安装目录。
- 切到 Deployment 标签页 → 点 + → Artifact,选上一步建好的 Web 应用。
- Application context 填
/supermarket,访问路径就是http://localhost:8080/supermarket/。 - 启动,控制台出现
Server startup in xxx ms说明部署成功。
最容易翻车的是 WebRoot/WEB-INF/lib 里的 jar 没有被打进 Artifact。很多人的 jar 明明放在 lib 目录,但启动后照样报 ClassNotFound。这时要在 Artifact 设置里手动把 lib 下的 jar 加入,右键 Put into /WEB-INF/lib。
提示:每次调整过 Artifact 打包内容后,务必执行 Build → Rebuild Project。IDEA 对 Artifact 的增量更新偶尔不生效,脏包会引发各种奇怪的 404 和 ClassNotFound,重建一次通常就解决。
4. 核心业务模块实现:商品管理、收银台与库存联动的代码路径
这类超市系统的业务模块看着多,本质是同一套 CRUD 模板的重复:商品、供应商、会员、类别,全是"列表页 + 新增/编辑表单 + 删除按钮"。真正有技术含量的是两处——收银台的库存扣减事务,以及登录拦截。这两处做得稳,演示时无论老师怎么点都不会翻车。
4.1 商品增删改查:一套 CRUD 模板吃遍所有模块
以删除为例,最典型的写法是 Servlet 里直接调 service:
// GoodsDeleteServlet.java protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int id = Integer.parseInt(req.getParameter("id")); // 列表页链接带过来的 GoodsService service = new GoodsService(); int rows = service.delete(id); // 删完重定向回列表,页面显示的永远是最新数据 resp.sendRedirect(req.getContextPath() + "/goods/list?msg=" + (rows > 0 ? "success" : "fail")); }删除用 doGet 是因为列表页里的删除入口就是普通链接点击。对应的 JSP 片段:
<a href="${pageContext.request.contextPath}/goods/delete?id=${goods.id}" onclick="return confirm('确定删除该商品吗?')">删除</a>onclick 里的 confirm 是前端最后一道防线。很多课设删数据零提示,演示时手滑点到就尴尬了。严谨的生产做法是删除用 POST 加表单提交,但课设场景普遍用链接加 confirm,够用。
新增和修改的后端骨架完全一样:新增是INSERT INTO goods VALUES (?,?,?,?),修改是UPDATE goods SET name=?, price=?, stock=? WHERE id=?,换的只是 SQL 语句和参数个数。你照着 DAO 里已有的 insert 复制一份改成 update,一个修改功能十分钟就能写完。改完记得把两个隐藏坑处理掉:价格字段用BigDecimal接收,别用 float;库存字段用Integer.parseInt前先判空,否则传空字符串会抛 NumberFormatException。
4.2 收银台扣库存:事务一定要放在 service 层
收银台是核心场景:前端把购物车提交过来,后端要做两件事——写入销售单、扣减商品库存。这两步必须同生共死,是一个事务。很多课设只写了插入销售单,扣库存漏了,演示时商品库存永远不变,业务上就是漏洞。
事务的正确写法,是把 Connection 的控制权从 DAO 收到 service,并且让多个 DAO 复用同一个连接:
// SaleService.java —— 事务边界在 service,DAO 只负责执行 SQL public void checkout(List<CartItem> items) throws SQLException { Connection conn = new DbUtil().getConnection(); conn.setAutoCommit(false); // 先关自动提交 try { SaleDao saleDao = new SaleDao(conn); // 关键:DAO 复用同一连接 GoodsDao goodsDao = new GoodsDao(conn); for (CartItem item : items) { saleDao.insert(item); // 写销售明细 int rows = goodsDao.decreaseStock(item.getGoodsId(), item.getNum()); if (rows == 0) { throw new RuntimeException("库存不足: " + item.getGoodsName()); } } conn.commit(); // 全部成功才提交 } catch (Exception e) { conn.rollback(); // 任何一步失败全部回滚 throw e; } finally { conn.setAutoCommit(true); // 恢复现场,避免脏状态 conn.close(); } }这段是整个超市系统里最值钱的代码。答辩老师十有八九会问:"你怎么保证并发情况下库存不超卖?"答案在 DAO 的条件更新里:
public int decreaseStock(int goodsId, int num) throws SQLException { String sql = "UPDATE goods SET stock = stock - ? WHERE id = ? AND stock >= ?"; // 返回 0 表示受影响行数为 0,说明现有库存不够 }用受影响行数判断库存是否充足,比"先 SELECT 查库存 → 判断 → 再 UPDATE"安全得多。后者在高并发下会读到旧值,两个请求同时判断库存够,然后一起扣成负数。条件更新把判断和操作合并成一条原子 SQL,从根上挡住了超卖。这个点讲清楚,比堆十个功能页面都加分。
4.3 登录拦截:一个 Filter 挡住所有未登录请求
Session 登录是 JavaWeb 必考点。登录成功的动作很简单:
// LoginServlet.java 登录成功分支 req.getSession().setAttribute("adminUser", admin); resp.sendRedirect(req.getContextPath() + "/admin/index.jsp");然后在 web.xml 注册一个 Filter 拦截后台路径:
// LoginFilter.java —— 未登录一律跳回登录页 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; HttpSession session = req.getSession(); Object user = session.getAttribute("adminUser"); String uri = req.getRequestURI().substring(req.getContextPath().length()); // 放行登录页、登录接口和静态资源,其余全部拦截 if (uri.startsWith("/login") || uri.startsWith("/css") || uri.startsWith("/js") || uri.startsWith("/images") || user != null) { chain.doFilter(request, response); } else { resp.sendRedirect(req.getContextPath() + "/login.jsp"); } }Filter 的 url-pattern 如果写/*,一定要记得放行 css、js、images 这些静态资源,否则登录页加载出来没有样式,CSS 全部 404,页面丑得没法演示。Session 超时是个隐藏坑:Tomcat 默认 30 分钟,演示到一半跳回登录页是正常机制,不是 Bug。想延长可以在 web.xml 里加 session-config 配置,或临时把 Tomcat 的全局 session 超时调大。
5. 跑通这套系统的避坑记录:驱动、乱码与部署路径的 5 个实战问题
这类课设项目九成以上的报错集中在少数几个点上,而且报错信息几乎一样。下面按我实际帮人排查的经验,把高频问题按"现象 → 原因 → 解决"列清楚,照着定位比盲目重装环境靠谱得多。
5.1 现象:启动报 ClassNotFoundException: com.mysql.jdbc.Driver
启动 Tomcat 时日志抛出ClassNotFoundException: com.mysql.jdbc.Driver,或者启动正常,第一次点查询就报这个。
原因:MySQL 驱动 jar 没有打进 Web 应用的 WEB-INF/lib。很多人以为把 jar 放进项目目录就行,但 IDEA 打包 Artifact 时没把它算进去,运行期根本找不到这个类。
解决:Project Structure → Artifacts → 选你的 Web 应用 → 在 Available Elements 里找到 mysql-connector 相关 jar → 右键 Put into /WEB-INF/lib → Rebuild Project。验证方法:启动后到 Tomcat 的部署目录看一眼,WEB-INF/lib 下是否有 mysql-connector-java 的 jar 文件。
5.2 现象:页面或数据库里的中文全是问号
登录页显示正常,但新增的商品名称入库后变成???,或者列表页中文乱码。中文乱码是 JavaWeb 里最玄学的问题之一,因为它不是单一原因。
原因:三层里至少一层没配对——JDBC URL 少了characterEncoding=utf8、JSP 页面没有声明 UTF-8、Tomcat 的 GET 请求解码不是 UTF-8、数据库表本身的字符集是 latin1。
解决:按顺序逐层排查。先在 JDBC URL 里补上useUnicode=true&characterEncoding=utf8;JSP 头加<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>;Tomcat 的 conf/server.xml 里 Connector 加URIEncoding="UTF-8";最后确认表和库的字符集,老脚本可能建成了 latin1,执行ALTER TABLE goods CONVERT TO CHARACTER SET utf8mb4;转换一次。
注意:Servlet 里的
setCharacterEncoding必须在读取任何参数之前调用。但这条只对 POST 请求生效,GET 请求里的中文参数是由 Tomcat 的 URIEncoding 决定的,和 Servlet 里那行无关,这是很多人忽略的分工。
5.3 现象:Tomcat 启动报 Port 8080 already in use
控制台红字报Port 8080 required by Tomcat is already in use,或者启动后一直卡住。
原因:8080 被占用了,可能是之前误杀了 IDEA 但 Tomcat 进程没退干净,也可能是别的服务用了这个端口。
解决:Windows 下先查占用,netstat -ano | findstr 8080找到 PID 后taskkill /F /PID <pid>;或者干脆改端口,编辑 Tomcat 的 conf/server.xml,把 Connector 的 port 从 8080 改成 8082。改完以后访问地址跟着变,项目代码不用动。IDEA 里改端口的位置在 Run Configuration 的 HTTP port 设置。
5.4 现象:启动成功但访问任意页面都是 404
Tomcat 正常起来了,日志没有异常,但浏览器访问http://localhost:8080/或者某个功能页就是 404。
原因:八成是部署的 Application context 和页面里写死的路径对不上。比如代码里全是/supermarket/index.jsp,但你的 Application context 设成了/,实际路径变成了/supermarket/supermarket/index.jsp。
解决:统一路径只留一处。推荐把 Application context 设为/supermarket,所有 JSP 里的链接一律用${pageContext.request.contextPath}开头,不写死任何一级路径。改完重启,再访问http://localhost:8080/supermarket/验证。
5.5 现象:MySQL 8.0 连接失败,报 Communications link failure
本机 MySQL 是 8.0,按老配置连不上,报Public Key Retrieval is not allowed、Access denied或者时区相关异常。
原因:MySQL 8.0 默认认证插件是 caching_sha2_password,老驱动 5.x 不认识;另外 8.0 驱动要求 URL 显式指定时区,否则把本地时区当异常抛出来。
解决:两条路任选一条。升级驱动为 8.x,driver 类名改成com.mysql.cj.jdbc.Driver,URL 加serverTimezone=Asia/Shanghai;或者把 root 用户改回旧认证方式,执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;。课设场景推荐后者,改动最小,还能保留旧驱动。
6. 把课设升级成简历项目:连接池替换与关键路径验证
跑通只是及格线,想把它变成简历上能讲的项目,有两个高性价比的改造方向。
第一个:把 DbUtil 里的裸 JDBC 换成 Druid 连接池。老写法每次 getConnection 都新建连接,课设演示没人关注,但面试必问。改造集中在 DbUtil 一个类里:
public class DbUtil { private static DruidDataSource dataSource = new DruidDataSource(); static { dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/supermarket" + "?useUnicode=true&characterEncoding=utf8"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setInitialSize(5); // 初始连接数 dataSource.setMaxActive(20); // 最大活跃连接数 } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); // 拿到的连接来自池,可复用 } }改造后注意两点:连接用完后依然要 close,但这时的 close 只是归还池子,不是真断开;事务里 commit、rollback 的写法完全不变。给 Druid 配一个监控页,还能在答辩时演示连接池的活跃连接数变化,属于加分项。
提示:连接池里的 close 只是归还连接,不是真正关闭。所以 finally 里关连接的代码不但不能省,反而更关键——不归还就相当于慢性泄漏。
第二个:给关键操作加操作日志。简历里写"系统包含操作日志"是一个具体的能力点,实现也不复杂:建一张 operate_log 表(id、admin_id、action、detail、create_time),在删除商品、结账这些敏感操作的 service 方法里各插一条记录。不用做得重,能说清楚"谁在什么时间删了哪个商品、结了多少金额的账"就够了。
最后是我的一个习惯:交课设前把功能按"登录 → 新增商品 → 进货 → 收银 → 查看订单"的顺序完整走三遍,同时开着 MySQL 命令行观察数据变化——新增后 goods 表多一行,收银后库存减、订单表增。数据在库里真实变动,比任何截图都有说服力。答辩时主动说出"库存扣减用了条件更新和事务,从根上防止超卖",这一句话的含金量,超过你堆出来的十个功能页面。
把这套 JavaWeb 超市管理系统从解压、导库、部署到改造完整过一遍,你对增删改查、Session、事务、Filter 的理解就不再是课本上的名词,而是一条条亲手调过的真实代码路径。希望帮到你。
本文还有配套的精品资源,点击获取