简介:面向Java学习者与毕业设计学生的一份JSP项目资源,针对大量中小超市缺乏信息化管理工具的现状,围绕进销存、销售与财务等核心业务提供可运行系统方案。系统基于JSP与B/S架构,涵盖商品入库、销售管理、库存监控和财务统计等模块,有助于经营者减少库存积压、降低资金风险,同时让员工高效完成日常操作。开发过程注重业务需求整理与流程设计,从功能模块划分到数据库表关系均有清晰呈现,结构上便于按模块阅读与二次开发。压缩包约29.11MB,包含系统源码与配套论文,论文涵盖需求分析、模块设计、数据库设计等关键章节,可作为毕业设计文档的重要参考。已有30人学习下载,适合Java Web初学者及毕业设计学生,可对照源码理解Servlet/JSP交互、数据库连接池等细节,大幅缩短项目搭建与论文撰写时间。
1. 为什么我推荐把这份 JSP 中小型超市管理系统源码当毕设底子
做了几年 Web 项目,回头再看 JSP 这套老技术栈,反而更觉得它适合用来做毕业设计。这份 JSP 中小型超市管理系统(源码+论文)属于典型的「麻雀虽小、五脏俱全」——进销存、销售单、库存、财务流水这些中小超市真正需要的模块都有,技术栈是 JSP + Servlet + JDBC + MySQL,正好卡在大学课程教的那套东西上。我拆过不少类似项目,坦白说,这种系统的难点不在功能多,而在「能不能跑通、代码能不能自圆其说、论文能不能把设计讲清楚」。这份资源把源码和论文打包在一起,对需要交毕设的同学来说,价值就在于不用从零造轮子,可以拿现成的骨架去改功能、补图表、换页面样式,而不是对着空白项目发愁。
2. 打开工程前先摸清技术栈:JSP + Servlet + MySQL 的搭配为什么还在用
2.1 这套技术栈的定位:适合演示、适合答辩、适合小规模并发
很多人一听 JSP 就皱眉,觉得过时。但放到毕业设计的场景里,JSP 恰恰是「最不容易翻车」的选择。你得想清楚一件事:毕设评委看的是你懂不懂 Web 开发的基本原理,而不是你用了多新的框架。JSP 页面由服务器动态编译成 Servlet,请求从浏览器发到 Tomcat,Servlet 处理业务后把数据转发回 JSP 渲染。这个链路足够短,短到答辩时你能拿着流程图讲清楚每一次跳转。
超市管理系统的业务量在真实环境里不大,集中在库存查询、商品入库、销售结算这几件事上,典型的并发也就几十个人同时操作。JSP + Servlet 默认的每个请求一个线程的模型,完全够用。反而是 MyBatis、Spring Boot 这一套,如果理解不透彻,答辩被追问到自动配置原理时容易卡壳。MySQL 这边用 InnoDB 引擎,事务对销售结算这种「要么扣库存、要么不扣」的场景是刚需。
2.2 解压后的资源包布局:源码、数据库脚本、论文放在哪
拿到压缩包,别急着双击运行,先花三分钟把目录结构看明白。这套资源解压后一般是三块内容:源码工程、数据库脚本、论文文档。源码工程里最关键的是两个目录,一个是src,存放 Java 源码(Servlet、DAO、JavaBean 都在这里);另一个是 WebRoot 或 webapp,推荐命名是WebRoot,里面是 JSP 页面和 WEB-INF 目录,WEB-INF 下必须有web.xml和lib两个东西,lib里放着 JDBC 驱动、JSTL 标签库这些 jar 包。
数据库脚本通常是一个.sql文件,文件名可能是temp.sql或者db_supermarket.sql,里面是建库、建表、插入初始数据的语句。论文部分往往是 Word 文档,包含开题报告、系统设计、数据库设计、测试报告这几个章节。我一般习惯先把 SQL 脚本打开扫一遍,看看表名和字段命名,再对照论文里的数据字典,这样后面改代码时找字段不会像无头苍蝇。
2.3 运行前必须锁死的版本组合:JDK、Tomcat、MySQL 的兼容坑
这种老项目最怕版本不匹配。我的经验是优先用这套组合:JDK 1.8(Java 8)、Tomcat 8.5、MySQL 5.7。如果非要用 MySQL 8.0,那就要在数据库连接串上多配置一个时区参数,否则启动时会报The server time zone value的错。下面是我常用的版本搭配表。
| 组件 | 推荐版本 | 备选方案 | 注意事项 |
|---|---|---|---|
| JDK | 1.8 | 1.7 | 高版本 JDK 编译旧代码可能有unsupported报错 |
| Tomcat | 8.5 | 9.0 | 8.5 对老项目的兼容性最好 |
| MySQL | 5.7 | 8.0 | 8.0 需要加serverTimezone=Asia/Shanghai |
| JDBC 驱动 | mysql-connector-java 5.1.49 | 8.0.x | 驱动包版本要和 MySQL 版本匹配 |
| IDE | MyEclipse / Eclipse | IDEA | IDEA 导入需要选对 Web 工程类型 |
注意:先统一好版本再动手。你在一台机器上折腾了两小时,最后发现是 MySQL 驱动包版本不对,这种亏我吃过不止一次。
3. 把系统跑起来:数据库脚本、Tomcat 配置与首次登录
3.1 初始化数据库:SQL 脚本的执行顺序与字符集设置
第一步先把数据库建好。解压包里那个.sql文件,里面通常已经包含了CREATE DATABASE和CREATE TABLE语句,所以你要做的就是执行它。打开 MySQL 命令行,先指定字符集再执行脚本,避免中文乱码。
mysql -u root -p CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARACTER SET utf8; USE supermarket; SET NAMES utf8; SOURCE D:/supermarket.sql;执行完后用SHOW TABLES;确认有没有生成商品表、员工表、销售表这些核心表。如果 SOURCE 执行报错,多半是脚本文件里带了 BOM 头或者字符集不匹配,用记事本把文件另存为 UTF-8 无 BOM 格式再执行一次。数据库表设计这块后面我会单独拆,先确认数据能进去就行。
3.2 修改数据库连接配置:驱动 jar 和连接串的关系
数据库准备好了,接下来要让 Java 代码能连上数据库。这类系统的连接配置一般写在src目录下的.properties文件或某个DBUtil.java类里。找到它,把数据库名字、账号、密码改成你自己的。连接串里的参数很关键,尤其是characterEncoding和useUnicode,少了它,插入中文进数据库就是一堆问号。
// 常见的一个 JDBC 连接工具类写法 public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/supermarket" + "?useUnicode=true&characterEncoding=utf8" + "&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { // 驱动类注册,5.x 驱动写 com.mysql.jdbc.Driver Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这段代码里,Class.forName负责把驱动类加载到 JVM,DriverManager.getConnection通过连接串里的地址、库名、账号密码建立连接。注意如果用的是 MySQL 8.0 驱动,驱动类名要改成com.mysql.cj.jdbc.Driver,并且连接串必须带上serverTimezone,否则跑起来就报时区错误,这是区分新旧驱动最直观的地方。
3.3 部署到 Tomcat 并启动:访问路径与上下文根的关系
连接配置改完,把工程发布到 Tomcat 的 webapps 目录下,或者在 IDE 里配置好 Tomcat 运行环境启动。如果你把工程文件夹命名为SuperMarket,那访问地址就是http://localhost:8080/SuperMarket/index.jsp。在 IDE 里部署的话,记住设置 Application context 为工程名。
# 启动 Tomcat 并查看日志 cd apache-tomcat-8.5.xx/bin ./startup.sh tail -f ../logs/catalina.out第一次启动重点看日志里有没有Exception字样。常见的两个现象:一个是连接数据库失败,看 Caused by 那行是驱动找不到还是拒绝连接;另一个是页面报 404,多半是访问路径写错了。启动成功后,浏览器访问首页,看到登录界面就说明框架搭通了。管理员账号一般是admin,密码123456或admin,具体看 SQL 脚本里插入的初始数据。
4. 核心代码与表结构拆解:登录权限、库存扣减和销售事务
4.1 登录会话控制:Session 与权限拦截的写法
超市系统的登录逻辑不复杂:表单提交用户名密码到 Servlet,Servlet 调 DAO 查询用户表,密码匹配就写入 Session,不匹配就返回错误提示。我拆过的项目里,权限控制往往只区分「管理员」和「普通员工」两种角色,菜单的可见性通过<c:if>标签判断角色值来实现。
// LoginServlet 的 doPost 核心逻辑 String username = request.getParameter("username"); String password = request.getParameter("password"); User user = userDao.findByUsernameAndPassword(username, password); if (user != null) { // 登录成功,把用户对象存进 session,供 JSP 页面读取 HttpSession session = request.getSession(); session.setAttribute("loginUser", user); session.setAttribute("role", user.getRole()); // role: 1-管理员, 0-员工 response.sendRedirect("admin/main.jsp"); } else { // 登录失败,转发回登录页并在 request 里带上错误信息 request.setAttribute("errorMsg", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); }这段代码里sendRedirect是重定向,浏览器地址栏会变成目标地址,适合登录成功后的跳转;forward是服务器内部转发,地址栏不变,适合把错误信息带回登录页面。这里有个细节:重定向后 request 里的属性会丢失,所以错误提示必须用forward传回去,这个坑在期末项目里出现频率极高。
4.2 商品管理与库存查询:DAO 层的 PreparedStatement 参数绑定
商品管理模块的核心是分页查询和模糊搜索。JSP 页面接收关键词和页码参数,传给 Servlet,Servlet 再传给 DAO。DAO 层用PreparedStatement做参数绑定,既能防 SQL 注入,又方便拼 LIKE 语句。
public List<Product> searchProducts(String keyword, int offset, int pageSize) { List<Product> list = new ArrayList<>(); // SQL 里的 ? 是占位符,由 PreparedStatement 统一转义 String sql = "SELECT * FROM product WHERE name LIKE ? LIMIT ?, ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, "%" + keyword + "%"); ps.setInt(2, offset); ps.setInt(3, pageSize); ResultSet rs = ps.executeQuery(); while (rs.next()) { Product p = new Product(); p.setId(rs.getInt("id")); p.setName(rs.getString("name")); p.setPrice(rs.getBigDecimal("price")); p.setStock(rs.getInt("stock")); list.add(p); } } catch (SQLException e) { e.printStackTrace(); } return list; }LIMIT ?, ?是 MySQL 的分页写法,第一个 ? 是起始位置,第二个 ? 是每页条数。注意 offset 的计算是(currentPage - 1) * pageSize,这个公式漏掉 Page-1 是分页错乱最常见的玄学。另外,库存字段在数据库里不要用浮点类型,INT就够,价格用DECIMAL(10,2),不然结算时会出现 0.1 + 0.2 = 0.30000000000000004 这种经典翻车。
4.3 销售单结算事务:扣库存与生成销售记录的原子性
超市系统的核心业务是销售结算——选了商品、填了数量、点了结账,系统要同时干两件事:往销售表插一条记录、把商品表里的库存减掉。这两步必须在一个事务里完成,否则就可能出现「钱收了但库存没减」或者「库存减了但销售记录丢了」的数据不一致。
public boolean settleSale(int productId, int quantity, double totalPrice) { Connection conn = null; try { conn = DBUtil.getConnection(); // 关闭自动提交,全部操作成功后再统一提交 conn.setAutoCommit(false); // 第一步:插入销售明细 String insertSql = "INSERT INTO sale_record(product_id, quantity, total_price, sale_time)" + " VALUES(?, ?, ?, NOW())"; PreparedStatement ps1 = conn.prepareStatement(insertSql); ps1.setInt(1, productId); ps1.setInt(2, quantity); ps1.setBigDecimal(3, BigDecimal.valueOf(totalPrice)); ps1.executeUpdate(); // 第二步:扣减库存 String updateSql = "UPDATE product SET stock = stock - ? WHERE id = ?"; PreparedStatement ps2 = conn.prepareStatement(updateSql); ps2.setInt(1, quantity); ps2.setInt(2, productId); int rows = ps2.executeUpdate(); if (rows == 0) { // 商品不存在或库存不足,回滚 conn.rollback(); return false; } conn.commit(); return true; } catch (SQLException e) { try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { try { if (conn != null) conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }这段代码的关键是setAutoCommit(false)之后,前面的操作都不会真正落库,只有commit()才生效;中间任何一步报错,rollback()会把所有操作撤销。扣库存那条 UPDATE 语句里,我一般还会在后面加上AND stock >= ?作为库存充足的条件判断,这样更保险,也算给销售流程加了一道校验。事务是这套系统里最值得在论文里展开讲的业务点。
5. 数据表设计复盘:进销存数据是怎么串起来的
5.1 从订单生单到报表统计的数据流转路径
超市管理系统的表结构通常围绕「商品—库存—销售—用户」这几条主线展开。商品表存基础信息,库存表可以并入商品表或者单拆一张库存流水表,销售表存每笔销售记录。我拆过的这类项目,表数量一般控制在 6 到 10 张,再多就是过度设计,再少就支撑不起论文里的「系统设计」章节。下面这张表是典型的表结构划分。
| 表名 | 作用 | 关键字段 |
|---|---|---|
| user | 用户/员工登录 | id, username, password, role |
| product | 商品信息 | id, name, price, stock, category |
| supplier | 供应商 | id, name, contact, phone |
| sale_record | 销售记录 | id, product_id, quantity, total_price, sale_time |
| purchase_record | 进货记录 | id, product_id, quantity, cost_price, purchase_time |
这些表之间靠外键和 ID 关联:销售记录里的product_id指向商品表的id,进货记录同理。报表统计的思路就是从sale_record里按日期分组,GROUP BY DATE(sale_time),然后SUM(total_price)得到每天的营业额。如果论文里需要画折线图,数据就是从这条路径取出来的。
5.2 数据库设计中的两类常见坏味道
第一类是表里大量出现「宽字段」——比如把商品的所有属性都塞在一个details字段里用逗号分隔,一旦要按某个属性查询,SQL 就写不出来,只能全表扫描再用 Java 去拆字符串。第二类是缺少必要的索引。销售表里的sale_time和product_id是查询高频字段,但没有建索引,数据量一上来,WHERE sale_time BETWEEN ...这类语句就会慢得离谱。初始化脚本里一般没有建索引语句,建议你手动补上:
ALTER TABLE sale_record ADD INDEX idx_sale_time (sale_time); ALTER TABLE sale_record ADD INDEX idx_product_id (product_id);建完索引之后,用EXPLAIN关键字看一下执行计划,确认type列从ALL变成了ref或range,说明索引生效了。这一步放在论文的测试章节里写,非常加分。
6. 避坑排查:从 404 到中文乱码的五个翻车现场
6.1 首页能打开但登录后 404:Servlet 映射路径对不上
现象:输入admin/main.jsp能访问页面,但点提交按钮后跳到一个 404 页面,地址栏里的路径明显不对。 原因:表单的action写的是login,但web.xml里<url-pattern>配置的是/LoginServlet,大小写也不一致,Tomcat 匹配不到对应的 Servlet。 解决:把表单 action 的值和 web.xml 里的 servlet-mapping 路径严格对齐。我一般习惯把action写成${pageContext.request.contextPath}/login,这样无论部署在哪个上下文根下都能正确拼接路径。
6.2 页面中文显示乱码,数据库里也是问号
现象:JSP 页面上的中文全部是菱形问号,插入数据库的中文也变成???。 原因:三层编码不统一——JSP 页面本身的pageEncoding不是 UTF-8,数据库连接串没加characterEncoding=utf8,MySQL 表的字符集是 latin1。 解决:三层一起改。JSP 文件头写pageEncoding="UTF-8",数据库表ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4,连接串加上useUnicode=true&characterEncoding=utf8。任何一个环节漏掉,乱码就会换个地方出现。
6.3 Tomcat 启动报 ClassNotFoundException:com.mysql.jdbc.Driver
现象:启动 Tomcat 后日志里出现ClassNotFoundException: com.mysql.jdbc.Driver。 原因:JDBC 驱动 jar 没有放到 WEB-INF/lib 目录下,或者 IDE 里没把 jar 加进构建路径。 解决:确认 mysql-connector-java 版本对应的 jar 文件被复制到WebRoot/WEB-INF/lib下。注意如果 lib 下同时有两个版本的驱动 jar,也会引起类加载冲突,删掉旧的只留一个。检查方法很简单:解压后的工程里搜索com/mysql/jdbc/Driver.class是否存在。
6.4 Eclipse 或 IDEA 直接启动时页面能开但 CSS 样式全丢失
现象:页面文字能显示,但原本的样式、图片全都乱了,控制台报一堆 404。 原因:CSS、图片这些静态资源放在 WebRoot 目录下的css、images文件夹里,但在 JSP 里引用的路径写成了绝对路径/css/style.css,部署后的上下文根拼接不上。 解决:JSP 里静态资源引用统一写成${pageContext.request.contextPath}/css/style.css,也就是 JSTL 的<c:url>标签负责拼上下文根。手工挨个改 JSP 里的 href 和 src 属性就行。
6.5 MySQL 8.0 下启动报 The server time zone value
现象:数据库连接时提示The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,服务起不来。 原因:MySQL 8.0 驱动强制要求显式指定时区,而旧连接串里没有这一项。 解决:连接串末尾加serverTimezone=Asia/Shanghai或者serverTimezone=GMT%2B8。如果你是用 MySQL 5.7,这条可以不写,但写上也无害。这个错在毕设答辩前一晚出现的概率极高,属于那种看着吓人、实际一行参数就能解决的问题。
7. 从运行到改造成自己的项目:加功能、做验证、补论文截图
7.1 给系统加一个「今日统计」面板的思路
这套系统跑通之后,别停留在「能登录、能查商品」就收工。我建议第一步加一个销售统计面板,放在首页最显眼的位置。逻辑不复杂:写一个StatisticsServlet,查三组数据——今日销售额、今日订单数、当前库存低于预警值的商品数,用一个HashMap返回给 JSP。JSP 里用 JSTL 的<c:forEach>遍历展示。这个功能很简单,却能让系统从「增删改查」升级成「有经营分析味道」,答辩时能多讲五分钟。
7.2 验证库存扣减逻辑:设计一个有说服力的测试流程
改造完功能,可以做一个针对性的验证:拿一个库存为 10 的商品,先做一次数量为 3 的销售,再查商品表库存变成 7;再用数据库客户端直接删掉该商品记录,再次尝试销售,确认系统回滚并且页面出现友好提示。把这个测试过程和截图贴到论文的「系统测试」章节里,比任何描述都有说服力。
7.3 收尾的一个习惯动作
我从那以后每次交付这类 JSP 项目,都会强制走一遍「清空数据库 → 重新执行 SQL 脚本 → 重启 Tomcat → 完整走一遍登录、进货、销售、统计」的流程,确认在干净环境下也能跑通,然后才敢打包。这套系统的源码和论文正好都齐,你下载后建议也这么做一遍,既验证资源完整度,也给自己留一份完整的操作记录。希望这份拆解能帮到你——记住,JSP 不是万能的,但把 JSP 项目讲透彻、改利索,绝对够你体面地毕业。
本文还有配套的精品资源,点击获取