ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JavaWeb超市管理系统毕业设计:从三层架构到数据库部署全攻略

JavaWeb超市管理系统毕业设计:从三层架构到数据库部署全攻略 简介一套基于JavaWeb的超市管理系统毕业设计项目面向计算机、通信、人工智能、自动化等相关专业的学生、老师及从业者兼顾课程设计、大作业、毕业设计等场景。系统涵盖商品管理、会员管理、供应商管理、结账处理等核心模块代码经过调试测试运行稳定支持在本地环境快速部署便于二次开发。资源包为zip格式共386个文件主体包括49个java源文件、56个jsp页面、98个class编译文件、1个sql数据库脚本以及css、js、less/scss等前端样式与图标资源整体约10.36MB。项目采用JSPServletJavaBean的分层思想数据库脚本与源码配套目录结构清晰DAO、Servlet、JSP等层次分明可直接导入IDE运行并配合数据库完成全流程操作已有156人学习下载适合从零搭建Web项目、理解JavaWeb请求响应流程与分层设计也可在此基础上扩展促销、库存预警、销售统计等新功能。项目整体具备较高借鉴价值尤其适合需要一份可运行、可讲解的完整毕设案例的开发者。1. javaweb 超市管理系统毕业设计选题里的「稳妥但想拿高分」难题每年毕业设计选题清单里超市管理系统几乎从不缺席。很多人觉得这个题目太老、太普通但真正让它翻车的从来不是业务复杂度而是两件事数据库设计能不能讲清楚项目能不能在别人电脑上一次性跑起来。这个题目的完整形态是一门基于 JavaWeb 的进销存系统JSP 做页面、Servlet 接收请求、JDBC 连接 MySQL围绕商品、供应商、进货、销售、库存这几条业务线把增删改查做透再加登录权限和几条统计 SQL。它适合两类人一类是选题清单里正好有它、想拿一个完整可复现方案的学生另一类是手里有源码但跑不起来、想搞清楚 IDEA 里到底怎么配置的从业者。这篇文章我把选型理由、表结构、部署步骤和踩坑点一次讲完。2. 技术选型与三层架构为什么这套组合在答辩时反而更好讲2.1 用 JSP Servlet MySQL而不是一上来就 Spring Boot很多人在开题时纠结既然市场上全是 Spring Boot毕设为什么还要写 Servlet我的建议很直接如果是课程设计你当然可以上 Spring Boot但毕业设计答辩时「为什么不直接用 Spring Boot」是一个必被追问的问题而 Servlet JSP 恰恰能让你把 Java Web 的底层链路完整讲出来——请求怎么进 Tomcat、Servlet 怎么匹配、Session 怎么保持登录状态、JDBC 怎么拿连接。这些是 Spring Boot 帮你藏起来的黑匣子答辩老师最喜欢问的就是黑匣子里面的东西。这套组合的业务承载也很清晰超市管理系统的核心是进销存也就是商品资料维护、供应商管理、进货入库、前台销售、库存盘点。它不像电商系统那样有复杂的营销逻辑但胜在业务链条完整刚好能用 JavaWeb 的标准三层架构讲明白。用 JavaWeb 做还有一个现实好处参考资料极多从黑马 JavaWeb 笔记到各类 javaweb 项目完整案例 MySQL 版本遇到问题搜得到、问得着这对独立完成毕业设计的人来说比什么都重要。2.2 三层架构与包结构先定骨架再写代码我一般会先把包结构定死再开始写代码。常见的做法是分成 controlServlet、service业务逻辑、dao数据库访问、entity实体类、filter过滤器、util工具类六层JSP 放在 webapp 目录下。这样一个 javaweb 项目完整案例的目录结构大致长这样supermarket/ ├── pom.xml ├── src/main/java/com/example/supermarket/ │ ├── control/ # LoginServlet、GoodsServlet、SaleServlet 等 │ ├── service/ # 业务层接口与实现 │ ├── dao/ # 数据库访问对象 │ ├── entity/ # AdminUser、Goods、Supplier、Stock 等实体 │ ├── filter/ # AuthFilter 登录拦截 │ └── util/ # DBUtil 数据库工具类 ├── src/main/resources/ │ └── db.properties # 数据库连接配置 └── src/main/webapp/ ├── jsp/ # login.jsp、goods/list.jsp、sale/list.jsp └── static/ # css、js依赖方向是 control 调 serviceservice 调 daodao 返回 entity 给 JSP 渲染。容易写反的地方是让 JSP 直接调 dao这会导致页面里塞满 JDBC 代码答辩时被问「业务逻辑和展示为什么不分层」会很难看。另一个常见错误是 service 层形同虚设所有方法只是透传 dao 的调用我建议至少把「进货 更新库存」这种需要事务的操作写进 service这样三层架构才算真正立住。2.3 版本搭配与运行环境兼容性比追新更重要关于 IDEA 运行 JavaWeb 项目配置最让我头疼的其实是版本搭配。Tomcat 10 之后把javax.servlet迁移成了jakarta.servlet网上大量旧教程的import javax.servlet.http.HttpServlet在 Tomcat 10 里直接编译失败。我的建议是不要追新直接用成熟的 JDK 8 Tomcat 8.5 MySQL 5.7 组合驱动用 mysql-connector-java 8.0.x这个组合踩坑最少。组件推荐版本理由JDK1.8兼容性最好几乎所有教程都基于它Tomcat8.5.x支持javax.*包网上代码可直接用MySQL5.7 或 8.05.7 轻量8.0 也兼容注意驱动配置IDEA2021 之后版本对 Maven 和 Tomcat 插件支持成熟Maven3.6依赖管理必备避免手动拖 jar为什么不用最新版因为毕业设计的时间成本很高新版本带来的「新特性」对你没有任何收益反而可能因为一个包名迁移让你多花两天排查。JDK 8 和 Tomcat 8.5 虽然老但它们在 JavaWeb 生态里就是「零兼容成本」的代名词这一点在你看到别人源码能直接跑通时会深有体会。3. 数据库设计用 7 张表把超市进销存业务说清楚3.1 从业务到表进销存三条线怎么落成表超市管理系统的业务可以拆成三条线基础资料、出入库流水、实时库存。基础资料是管理员、供应商、商品流水是进货记录和销售记录库存是每个商品当前可卖数量。再加一张操作日志表来记录谁在什么时候干了什么这样答辩时讲「系统可审计」就有据可依。这些业务落到 MySQL 里就是 7 张表admin_user管理员、supplier供应商、goods商品、stock库存、purchase进货、sale销售、operation_log操作日志。这里有一个关键设计决策库存不通过「进货总和减销售总和」实时计算而是单独用一张stock表维护当前数量。原因有三查询快首页展示库存列表不需要聚合大表事务好讲进货和销售时同步更新库存这是答辩时最拿得出手的业务闭环演示直观改完数据立刻能看到库存变化。代价是多了一条更新语句但对于毕业设计来说完全值得。3.2 建表 SQL字段类型、精度与唯一约束建表是数据库设计落地的第一步也是最容易被低估的一步。字段类型选错后面全是坑价格用float会出现 0.1 加 0.2 不等于 0.3 的玄学问题数量用int会出现负数库存字符串长度不统一会导致联表查询对不上。下面是完整建表脚本CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARACTER SET utf8mb4; USE supermarket; CREATE TABLE admin_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE supplier ( id INT PRIMARY KEY AUTO_INCREMENT, supplier_name VARCHAR(100) NOT NULL, contact VARCHAR(50), phone VARCHAR(20), address VARCHAR(200), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, goods_name VARCHAR(100) NOT NULL, category VARCHAR(50), unit VARCHAR(10), price DECIMAL(10,2) NOT NULL, supplier_id INT, low_stock INT DEFAULT 10, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_goods_supplier FOREIGN KEY (supplier_id) REFERENCES supplier(id) ) ENGINEInnoDB; CREATE TABLE stock ( id INT PRIMARY KEY AUTO_INCREMENT, goods_id INT NOT NULL UNIQUE, quantity INT NOT NULL DEFAULT 0, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_stock_goods FOREIGN KEY (goods_id) REFERENCES goods(id) ) ENGINEInnoDB; CREATE TABLE purchase ( id INT PRIMARY KEY AUTO_INCREMENT, goods_id INT NOT NULL, quantity INT NOT NULL, purchase_price DECIMAL(10,2), total_amount DECIMAL(10,2), supplier_id INT, operator VARCHAR(50), purchase_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_purchase_goods FOREIGN KEY (goods_id) REFERENCES goods(id), CONSTRAINT fk_purchase_supplier FOREIGN KEY (supplier_id) REFERENCES supplier(id) ) ENGINEInnoDB; CREATE TABLE sale ( id INT PRIMARY KEY AUTO_INCREMENT, goods_id INT NOT NULL, quantity INT NOT NULL, sale_price DECIMAL(10,2), total_amount DECIMAL(10,2), operator VARCHAR(50), sale_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_sale_goods FOREIGN KEY (goods_id) REFERENCES goods(id) ) ENGINEInnoDB; CREATE TABLE operation_log ( id INT PRIMARY KEY AUTO_INCREMENT, operator VARCHAR(50), action VARCHAR(100), detail VARCHAR(500), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB;几个关键参数各家有各家的习惯但我的取舍是金额一律DECIMAL(10,2)整数部分 8 位小数 2 位足够覆盖超市日销售额数量用INT不需要负数的业务场景不要用INT UNSIGNED——这个很容易被忽略因为如果你用了无符号整数一旦代码里出现quantity quantity - 5且当前值小于 5MySQL 会直接报错而不是返回负数这个报错信息在答辩时会让你措手不及字符串一律utf8mb4不要用utf8因为utf8存不了 emoji虽然超市系统没表情但「中文 特殊符号」的场景utf8mb4更稳。建表之后免不了要改结构。MySQL 修改数据库结构用ALTER TABLE比如说商品表加一个折扣字段ALTER TABLE goods ADD COLUMN discount DECIMAL(3,2) DEFAULT 1.00;这个语法在答辩前调整功能时非常常用我建议你在自己的数据库里把「增删改查数据库结构」都练一遍因为老师可能会现场让你演示加一个字段或者改一个字段类型手写ALTER TABLE比可视化工具点击更能展示功底。3.3 外键、索引与事务把库存扣成负数就是从这里开始的我在上面建表时用了外键这也是很多毕设翻车的地方外键在插入和删除时会做引用检查比如你删一个供应商如果商品表里还有商品引用它MySQL 会拒绝删除并报Cannot delete or update a parent row。很多学生会觉得这是报错但答辩时这其实是一个加分点——你可以在演示时故意删一个有商品的供应商然后解释这就是外键的引用完整性约束说明系统不会产生孤儿数据。事务的落点有三个进货、销售、删除商品。进货先插purchase再更新stock这两步必须同时成功同时失败否则会出现「进货单有了但库存没加」的数据不一致。销售同理先插sale再扣库存。扣库存的 SQL 我一般这样写UPDATE stock SET quantity quantity - ? WHERE goods_id ? AND quantity ?;这样写的好处是如果库存不够受影响行数是 0代码里判断这个值就能抛「库存不足」的异常不需要先 SELECT 再 UPDATE。先查再改不是不行但多一次查询就多一个并发窗口虽然毕设系统没有并发压力但这样一个条件更新能让你的代码在逻辑层面无懈可击。索引方面sale.sale_time和goods.goods_name是查询频率最高的字段建索引后按时间查流水、按名称搜商品都会快很多在一个数据量不大的系统里效果不明显但索引设计是必问的考点。4. 用 IDEA 把 javaweb 项目跑通从建工程到登录跳转的最小路径4.1 建工程Maven-war 结构别手拖 jar我能给出的最真诚建议是用 Maven 建工程打包方式选war不要手动往 lib 目录里拖 jar 包。手动拖 jar 的痛点是换电脑、换项目、重新导入时 jar 丢失然后就是无休止的 ClassNotFoundException。用 Maven 的话依赖写在pom.xml里任何一台电脑上都能自动下载这才是 javaweb 项目完整案例 MySQL 版本能跑通的底气。pom.xml的核心依赖大概长这样project modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdsupermarket/artifactId version1.0.0/version packagingwar/packaging dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.29/version /dependency /dependencies /project注意javax.servlet-api的 scope 是provided意思是这个 jar 由 Tomcat 提供不需要打进 war 包也不参与打包。如果漏了providedwar 包会带着一份 servlet-api启动时极易和 Tomcat 自带的冲突表现就是各种奇怪的 NoClassDefFoundError。JSTL 必须有但不用写版本匹配逻辑1.2 是最后的稳定版本配合 JSP 2.3 用没有任何问题。4.2 数据源配置与 JDBC 工具类数据库连接配置放在resources目录下命名db.properties这样和代码分离答辩时解释「配置与代码解耦」也顺理成章jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/supermarket?useSSLfalsecharacterEncodingUTF-8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456这里serverTimezoneAsia/Shanghai是 MySQL 8.0 驱动必须加的不加会报时区错误characterEncodingUTF-8保证中文写入数据库不乱码useSSLfalse是去掉无意义的加密握手警告。对应工具类public class DBUtil { private static final Properties props new Properties(); static { try { InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties); props.load(in); Class.forName(props.getProperty(jdbc.driver)); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection( props.getProperty(jdbc.url), props.getProperty(jdbc.username), props.getProperty(jdbc.password) ); } public static void close(Connection conn, Statement st, ResultSet rs) { // 省略三个对象逐个判断非空后 close注意关闭顺序 rs - st - conn } }这个工具类里Class.forName在 MySQL 8.0.29 驱动下可以省略不写但保留它是一种习惯因为旧项目或者旧驱动实例里经常需要这行。关闭资源的顺序是ResultSet → Statement → Connection顺序反了会在连接池场景造成连接泄漏虽然毕设里一般不用连接池但这个习惯值得养成。4.3 从配置 Tomcat 到出现登录页IDEA 运行 JavaWeb 项目配置里最核心的一步是「把工程部署到 Tomcat」。具体操作是打开 Run → Edit Configurations → 点加号选 Tomcat Server → Local → 在 Deployment 页签点加号选 Artifact选择supermarket:war exploded然后修改 Application Context 为/market。这里的war exploded是展开目录方式改 JSP 不用重启就能刷新调试体验比完整 war 好太多。然后写一个最简登录页和登录 Servlet让项目先转起来。login.jsp只放表单LoginServlet接收请求WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); AdminUser user new AdminUserDao().findByUsernameAndPassword( username, password); if (user ! null) { req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /goods/list); } else { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }这里的两个跳转方式值得说清楚登录失败用forward请求转发到login.jsp地址栏不变刷新浏览器会再次提交表单登录成功用sendRedirect重定向到商品列表页地址栏变为新地址刷新不会重复提交。很多演示翻车就是因为把成功也写成forward然后刷新一次就弹「确认重新提交表单」老师当场印象分就没了。光有登录还不够还要有一个过滤器拦截未登录访问WebFilter(/*) public class AuthFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; String uri req.getRequestURI(); if (uri.endsWith(/login.jsp) || uri.contains(/login) || uri.contains(/static/)) { chain.doFilter(request, response); return; } if (req.getSession().getAttribute(loginUser) null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }过滤器的作用是统一做登录校验避免在每个 Servlet 里重复写判断。放行规则里必须包含login.jsp和静态资源否则登录页面本身也会被拦截形成死循环。把这三个文件配好、启动 Tomcat、浏览器访问http://localhost:8080/market/login.jsp看到登录页那一刻你的 JavaWeb 项目就算真正跑通了。5. 避坑与排查答辩前最容易翻车的 5 个常见问题5.1 中文乱码jsp、请求、数据库三层一起设中文乱码是 JavaWeb 里最经典的翻车现场。现象是页面显示中文正常但往数据库里一插存进去的全是??或者反过来数据库里正常但页面上全是乱码。原因是 JSP 页面编码、HTTP 请求编码、数据库编码三层只要有一层不一致就会出问题。JSP 页面默认用ISO-8859-1数据库建库时如果用了latin1也是坑POST 请求参数没有指定解析编码时同样会乱。解决方法是三层统一到 UTF-8。JSP 文件头部写% page contentTypetext/html;charsetUTF-8 %过滤器中设置request.setCharacterEncoding(UTF-8)注意只在 POST 请求时需要Tomcat 8 之后 GET 请求的 URI 编码默认已经是 UTF-8数据库连接 URL 带上characterEncodingUTF-8建库时用utf8mb4。这三处一次调对后面所有页面都不会再为中文操心。5.2 404 和 405上下文路径与 Servlet 映射对不上404 的现象很直接Tomcat 起来显示启动成功浏览器一访问就白页 404。原因多半是 Deployment 里 Application Context 配置和实际访问路径不一致。比如你配置的 Context 是/supermarket但代码里写的是/market/login.jsp那必然 404。解决方法是统一路径来源JSP 页面里不要写死绝对路径用${pageContext.request.contextPath}动态拼接。这个 EL 表达式会输出当前应用的上下文路径不管 Context 叫什么都能正确拼出 URL。405 则是另一个病页面显示「HTTP 状态 405 – 方法不允许」一般出现在你用 JSP 表单提交到某个 Servlet但 Servlet 只实现了doGet或只实现了doPost。解决方法是两个方法都实现或者干脆都调到同一个处理逻辑。5.3 数据库连接失败驱动类名、时区参数一个都不能少数据库连接失败是所有人第一次跑的时候都会遇见的坎。现象有两种ClassNotFoundException: com.mysql.jdbc.Driver或者The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。第一种原因是用 MySQL 8.0 驱动却写了 MySQL 5.7 的驱动类名。MySQL 8.0 的驱动类名是com.mysql.cj.jdbc.Driver旧的是com.mysql.jdbc.Driver驱动包里其实还保留了旧的名字但会打印一条烦人的警告。第二种原因就是连接 URL 里没有加serverTimezone参数。解决方法是直接按 4.2 节里的配置抄驱动类用com.mysql.cj.jdbc.DriverURL 里加上serverTimezoneAsia/Shanghai。如果还连不上用命令行先测mysql -u root -p能不能登录排除密码错误的低级问题。这个排查顺序能省下至少半小时。5.4 库存扣成负数事务没提交 缺少条件 UPDATE这个坑我用血泪经验来谈销售页面能提交超过库存数量的购买请求库存变成负数总金额照样算出来。原因是两笔操作没有放在同一个事务里或者扣库存的 SQL 没有加库存条件。解决方法是两层配合。第一层把「插入 sale 记录」和「扣减 stock」放进同一个事务任何一个失败都rollback第二层扣库存 SQL 用UPDATE stock SET quantity quantity - ? WHERE goods_id ? AND quantity ?当库存不足时受影响行数是 0代码里检查这个值并抛异常。下面是事务代码骨架Connection conn DBUtil.getConnection(); conn.setAutoCommit(false); try { saleDao.insert(conn, sale); // 插入销售记录 int rows stockDao.reduce(conn, goodsId, quantity); // 条件扣减 if (rows 0) { throw new RuntimeException(库存不足); } conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); DBUtil.close(conn, null, null); }注意setAutoCommit(false)必须在所有 SQL 执行前调用rollback之后把连接归还前要恢复setAutoCommit(true)否则连接池复用时会带着错误的事务状态继续干活。5.5 删除被外键拦下把报错讲成加分项现象是删除一个供应商时系统直接报Cannot delete or update a parent row: a foreign key constraint fails。很多学生看到这个报错就慌其实这是数据库在保护数据完整性。原因是商品表goods.supplier_id外键引用供应商表被引用时不允许删除。解决方法是设计上就明确规则供应商还有商品在售时不能删除前端把删除按钮置灰并提示「该供应商下有商品不能删除」如果一定要删先把对应商品转移或删除。这个场景在答辩时特别好用。你可以故意演示一次失败删除然后解释外键约束的作用再展示「先处理引用数据再删除」的正确流程这一来一回就把「数据库设计」这个必考项讲透了。6. 一个 10 分钟的验收脚本把你的系统调到「可演示」状态答辩演示最怕的是临场出错而临场出错几乎都是因为没按固定脚本走一遍。我自己的习惯是准备一个验收脚本每次演示前严格按顺序过一遍全程不超过 10 分钟。步骤操作预期结果1执行init.sql重建数据库所有表重建无报错2IDEA 启动 Tomcat控制台无异常端口未被占用3访问/market/login.jsp出现登录页中文正常4输入admin/123456跳转商品列表页5新增商品「可口可乐」列表出现新品无乱码6进货 20 件库存变为 207销售 5 件库存变为 15销售额正确8将库存改为低于预警值首页或列表出现库存预警提示最后两步验收靠两条 SQL 收尾。查低库存商品SELECT g.goods_name, s.quantity, g.low_stock FROM stock s JOIN goods g ON s.goods_id g.id WHERE s.quantity g.low_stock;查今日销售流水和销售额SELECT goods_name, SUM(quantity) AS total_qty, SUM(total_amount) AS total_amount FROM sale s JOIN goods g ON s.goods_id g.id WHERE DATE(s.sale_time) CURDATE() GROUP BY goods_id;另外强烈建议保留一个reset.sql每次演示前清空流水和库存数据把系统恢复到「刚开业」的干净状态。我第一次答辩演练就是因为没有这个脚本演示到一半被提问打断回来时数据已经乱了只能现场把数据库重跑那种慌张至今记得。后来每次演示前我都按这个脚本先走一遍再也没在数据问题上翻过车。希望帮到你。本文还有配套的精品资源点击获取
返回列表