ARTICLE DETAIL

资讯详情

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

JavaWeb超市管理系统毕设实战:源码+数据库脚本从跑通到答辩

JavaWeb超市管理系统毕设实战:源码+数据库脚本从跑通到答辩 简介这份资源是基于JavaWeb的超市管理系统完整项目包面向计算机、通信、人工智能、自动化等相关专业的在校学生与教师可用于毕业设计、期末课程设计或课程大作业也适合希望进阶练手的Java初学者。项目为个人毕设作品答辩评审分达98分代码均经过调试测试可稳定运行。压缩包共64个文件约582KB其中28个java文件承载后端业务逻辑21个jsp页面负责前端交互展示10个xml与1个properties文件完成框架与项目配置另附1个sql数据库脚本及说明文档结构清晰、便于按模块阅读。目前已有141人学习下载。读者可从中获得一套可直接运行的超市管理系统源码与建库脚本理解JavaWeb分层设计与前后端协作方式并在此基础上修改调整实现商品、库存、订单等不同业务功能具备较高的学习借鉴价值。1. 从一份能跑起来的超市管理系统源码说起很多同学拿到「基于JavaWeb的超市管理系统【项目源码数据库脚本】毕业设计」这个题目时第一反应是去搜一堆现成项目结果下载下来发现要么跑不起来要么数据库对不上要么代码里全是看不懂的框架堆砌。我带过几届学生的毕设也帮不少朋友排查过这类项目最常见的翻车现场就是——源码能打开但一运行就报 500数据库连不上或者登录进去发现功能全是空壳。这个标题背后其实包含三样东西一套用 JavaWeb 技术栈实现的超市管理业务系统、一份可以直接导入的数据库脚本、以及一套能支撑毕业设计答辩的完整工程结构。它解决的核心问题是让你在有限时间内拥有一个功能完整、逻辑清晰、能演示、能讲清楚技术点的 Web 项目。适合的人群很明确——计算机相关专业做毕设的本科生尤其是选了 JavaWeb 方向、需要交源码和数据库脚本的同学。我一般会建议先别急着改代码而是把项目跑起来看到登录页、看到商品列表、看到订单记录再去理解每一层是怎么串起来的。这篇笔记就按这个思路走先讲清楚这套系统通常长什么样、技术选型为什么这么定再带你从数据库脚本一路跑到功能页面最后把踩过的坑和进阶技巧摊开说。你跟着走一遍至少能拿到一个能答辩、能二次开发的底子。2. 超市管理系统的技术选型与工程结构为什么是 Servlet JSP MySQL2.1 典型 JavaWeb 超市管理系统的分层与依赖市面上能搜到的「javaweb项目完整案例mysql」大多采用经典三层架构表现层用 JSP Servlet业务层用 Service 类持久层用 JDBC 或轻量级工具类直接操作 MySQL。这种组合看起来老但对毕业设计来说反而是优势——不依赖 Spring 全家桶环境配置简单代码逻辑直白答辩时老师问到的每一个点你都能在代码里指出来。一个标准的工程目录通常长这样SupermarketSystem/ ├── src/ │ ├── com/supermarket/controller/ // Servlet 控制器 │ ├── com/supermarket/service/ // 业务逻辑 │ ├── com/supermarket/dao/ // 数据库访问 │ ├── com/supermarket/entity/ // 实体类 │ └── com/supermarket/util/ // 工具类DBUtil 等 ├── WebContent/ │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ // 第三方 jar │ ├── css/ js/ images/ │ └── *.jsp // 页面 └── database/ └── supermarket.sql // 数据库脚本实体类对应数据库表比如Product、Order、User、Category。DAO 层写 SQLService 层做事务和业务判断Servlet 接收请求、调 Service、转发到 JSP。这套结构虽然不如 Spring Boot 简洁但胜在每一行代码你都能追踪到不会出现「注解一加逻辑消失」的情况。提示如果你的学校要求必须用 Spring Boot那这套结构需要调整但数据库脚本和业务表设计完全可以复用。选型没有绝对好坏关键是你能讲清楚为什么这么选。2.2 数据库脚本里必须有的表与字段设计数据库脚本是这套毕设的另一个核心交付物。很多同学从网上下的脚本只有建表语句没有初始数据导入后系统空空如也演示效果大打折扣。一份能直接用的supermarket.sql至少包含建库、建表、插入初始数据三部分。核心表通常有这些表名用途关键字段user系统用户管理员/收银员id, username, password, rolecategory商品分类id, name, remarkproduct商品信息id, name, category_id, price, stock, unitorder订单主表id, order_no, user_id, total_amount, create_timeorder_item订单明细id, order_id, product_id, quantity, pricesupplier供应商id, name, contact, phone建表时注意两点一是字符集统一用utf8mb4避免中文乱码二是外键约束在毕业设计里可以加但导入数据时容易因为顺序问题报错我一般会先禁用外键检查插完数据再启用。-- 建库与建表片段 CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARACTER SET utf8mb4; USE supermarket; SET FOREIGN_KEY_CHECKS 0; DROP TABLE IF EXISTS product; CREATE TABLE product ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 商品名称, category_id INT DEFAULT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, stock INT NOT NULL DEFAULT 0, unit VARCHAR(20) DEFAULT 件, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO product (name, category_id, price, stock, unit) VALUES (农夫山泉550ml, 1, 2.00, 200, 瓶), (康师傅红烧牛肉面, 2, 4.50, 150, 袋), (心相印抽纸, 3, 12.90, 80, 提); SET FOREIGN_KEY_CHECKS 1;这段脚本的逻辑很直接先建库关外键检查删旧表重建插入几条能立刻看到效果的商品数据最后恢复外键。参数上DECIMAL(10,2)保证金额精度stock用 INT 足够unit给默认值避免空值。导入时用命令行source或 Navicat 运行 SQL 文件都行注意文件编码选 UTF-8。2.3 在 IDEA 里跑通第一个页面的最小步骤环境配置是新手最容易卡住的地方。我一般按这个顺序来装 JDK 8 或 11、装 Tomcat 8.5/9、装 MySQL 5.7/8.0、IDEA 配好 Artifact。具体操作IDEA 打开项目File → Project Structure → Modules确认src是 Sources Root。Libraries 里添加WebContent/WEB-INF/lib下的所有 jar。Facets 里添加 WebWeb Resource Directory 指向WebContentweb.xml路径选对。Artifacts 里添加 Web Application Exploded输出目录默认。Run → Edit Configurations → 加 Tomcat LocalDeployment 里加刚才的 Artifact。启动前先在 MySQL 里执行supermarket.sql然后改DBUtil.java里的数据库连接地址、用户名、密码。// DBUtil.java 关键片段 public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 你的密码; public static Connection getConnection() throws SQLException { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } return DriverManager.getConnection(URL, USER, PASSWORD); } }注意serverTimezone参数MySQL 8.0 不写这个会报时区错误。驱动类名用com.mysql.cj.jdbc.Driver对应 mysql-connector-java 8.x。如果用的是 5.x 驱动类名是com.mysql.jdbc.DriverURL 里也不用加时区。启动 Tomcat 后访问http://localhost:8080/项目名/login.jsp能看到登录页就说明环境通了。3. 从数据库脚本到功能页面商品管理与订单结算的完整链路3.1 商品列表查询Servlet JSP 的数据传递商品管理是超市系统里最基础也最常被演示的功能。用户访问productList.jsp或某个 Servlet 映射路径后台从数据库查出所有商品再转发到页面渲染。这条链路走通了其他模块基本是复制粘贴改改 SQL。先看 Servlet 怎么写// ProductServlet.java 片段 WebServlet(/product/list) public class ProductServlet extends HttpServlet { private ProductService productService new ProductServiceImpl(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String keyword req.getParameter(keyword); ListProduct products; if (keyword ! null !keyword.trim().isEmpty()) { products productService.searchByName(keyword.trim()); } else { products productService.findAll(); } req.setAttribute(productList, products); req.getRequestDispatcher(/productList.jsp).forward(req, resp); } }逻辑说明doGet接收搜索关键词调 Service 层查询把结果放进request作用域转发到 JSP。参数keyword来自页面搜索框为空就查全部。这里没有做分页数据量大了会慢但毕设演示够用。如果要加分页可以在 Service 层加LIMIT参数。Service 和 DAO 层// ProductDaoImpl.java 片段 public ListProduct findAll() { ListProduct list new ArrayList(); String sql SELECT p.*, c.name AS categoryName FROM product p LEFT JOIN category c ON p.category_id c.id ORDER BY p.id DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); 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)); p.setCategoryName(rs.getString(categoryName)); list.add(p); } } catch (SQLException e) { e.printStackTrace(); } return list; }这里用LEFT JOIN把分类名一起查出来避免 JSP 里再查一次数据库。try-with-resources自动关闭连接防止资源泄漏。注意rs.getBigDecimal(price)对应实体类的BigDecimal类型别用getDouble否则金额会有精度问题。JSP 页面用 JSTL 遍历% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table trthID/thth名称/thth分类/thth价格/thth库存/th/tr c:forEach items${productList} varp tr td${p.id}/td td${p.name}/td td${p.categoryName}/td td${p.price}/td td${p.stock}/td /tr /c:forEach /tableJSTL 的c:forEach比脚本片段干净得多也更容易维护。如果页面显示乱码检查 JSP 顶部有没有% page contentTypetext/html;charsetUTF-8 %以及 Tomcat 的 URIEncoding 是否配成 UTF-8。3.2 订单结算事务控制与库存扣减的落地方案订单模块是超市系统的业务核心也是答辩时老师最爱问的地方。一个订单涉及三张表order插主记录order_item插明细product扣库存。这三步必须在一个事务里完成否则会出现「订单生成了但库存没扣」或者「库存扣了但订单没生成」的脏数据。我一般把事务放在 Service 层用 JDBC 的手动提交// OrderServiceImpl.java 片段 public boolean createOrder(Order order, ListOrderItem items) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 插入订单主表 String orderSql INSERT INTO order (order_no, user_id, total_amount, create_time) VALUES (?, ?, ?, NOW()); PreparedStatement psOrder conn.prepareStatement(orderSql, Statement.RETURN_GENERATED_KEYS); psOrder.setString(1, order.getOrderNo()); psOrder.setInt(2, order.getUserId()); psOrder.setBigDecimal(3, order.getTotalAmount()); psOrder.executeUpdate(); ResultSet rs psOrder.getGeneratedKeys(); int orderId 0; if (rs.next()) orderId rs.getInt(1); // 2. 插入明细并扣库存 String itemSql INSERT INTO order_item (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?); String stockSql UPDATE product SET stock stock - ? WHERE id ? AND stock ?; PreparedStatement psItem conn.prepareStatement(itemSql); PreparedStatement psStock conn.prepareStatement(stockSql); for (OrderItem item : items) { psItem.setInt(1, orderId); psItem.setInt(2, item.getProductId()); psItem.setInt(3, item.getQuantity()); psItem.setBigDecimal(4, item.getPrice()); psItem.addBatch(); psStock.setInt(1, item.getQuantity()); psStock.setInt(2, item.getProductId()); psStock.setInt(3, item.getQuantity()); if (psStock.executeUpdate() 0) { throw new SQLException(库存不足商品ID item.getProductId()); } } psItem.executeBatch(); conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }关键点setAutoCommit(false)开启事务任何一步抛异常都rollback全部成功才commit。扣库存的 SQL 带了AND stock ?防止超卖。RETURN_GENERATED_KEYS拿到刚插入的订单 ID用于关联明细。批量插入用addBatch和executeBatch比循环单条插入快。注意如果并发量稍大stock ?这种乐观锁方式可能不够需要加行锁SELECT ... FOR UPDATE。但毕设演示环境并发极低这样写足够而且逻辑清晰好讲。3.3 登录鉴权与角色区分过滤器 Session 的配合超市系统通常有两种角色管理员和收银员。管理员能管商品、管用户、看报表收银员只能做收银和查订单。实现方式用 Servlet Filter 拦截请求检查 Session 里的用户角色。// AuthFilter.java 片段 WebFilter(/*) public class AuthFilter implements Filter { private static final ListString WHITE_LIST Arrays.asList(/login.jsp, /login, /css/, /js/); Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 放行静态资源和登录页 for (String white : WHITE_LIST) { if (uri.contains(white)) { chain.doFilter(req, resp); return; } } User user (User) request.getSession().getAttribute(currentUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } // 角色校验非管理员禁止访问用户管理 if (uri.contains(/user/) !admin.equals(user.getRole())) { response.sendError(403, 无权限); return; } chain.doFilter(req, resp); } }逻辑说明白名单里的路径直接放行其他请求先查 Session 有没有登录用户没有就跳登录页。角色校验按 URL 前缀判断/user/开头的只有 admin 能进。参数上WHITE_LIST可以根据实际项目调整注意别把登录后的首页误放进去。登录 Servlet 里设置 Session// LoginServlet.java 片段 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); User user userService.login(username, password); if (user ! null) { req.getSession().setAttribute(currentUser, user); resp.sendRedirect(req.getContextPath() /index.jsp); } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } }密码在数据库里存明文是毕设常见做法但答辩时容易被问。我一般会建议至少用 MD5 加盐存一下代码量不大但能体现安全意识。登录成功后跳首页失败则转发回登录页并带错误提示。4. 避坑与排查数据库脚本导入、中文乱码、Tomcat 启动报错4.1 数据库脚本导入失败外键顺序与字符集问题现象用 Navicat 或命令行导入supermarket.sql报ERROR 1215: Cannot add foreign key constraint或ERROR 1064: You have an error in your SQL syntax。原因建表顺序不对子表先于父表创建或者脚本里用了 MySQL 8.0 不支持的旧语法字符集不统一导致中文注释解析出错。解决在脚本开头加SET FOREIGN_KEY_CHECKS 0;结尾加SET FOREIGN_KEY_CHECKS 1;。建表顺序按依赖关系排先user、category、supplier再product最后order和order_item。字符集统一写DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_general_ci。如果还报语法错检查是不是用了TYPEMyISAM这种旧写法改成ENGINEInnoDB。4.2 中文乱码从 JSP 到数据库的编码链路现象页面输入中文存进数据库变成???或者数据库里正常但页面显示乱码。原因编码链路有多个环节——JSP 页面编码、请求编码、数据库连接编码、表字符集。任何一个环节不一致都会乱码。解决按顺序检查。JSP 顶部加% page contentTypetext/html;charsetUTF-8 languagejava %。Servlet 里request.setCharacterEncoding(UTF-8)要放在读取参数之前。数据库连接 URL 加useUnicodetruecharacterEncodingutf8。建库建表用utf8mb4。Tomcat 的server.xml里 Connector 加URIEncodingUTF-8。这五处都对了乱码基本消失。4.3 Tomcat 启动报 ClassNotFoundExceptionjar 包缺失与路径错误现象Tomcat 启动时报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver或javax.servlet.ServletException。原因WEB-INF/lib下缺少 mysql-connector-java 或 jstl 的 jar或者 IDEA 的 Artifact 没有把 lib 目录打进去。解决确认WEB-INF/lib下有mysql-connector-java-8.x.jar、jstl.jar、standard.jar。IDEA 里 File → Project Structure → Artifacts → 选中 Exploded → 右侧 Available Elements 里把 lib 下的 jar 双击加到输出目录。如果用的是 Maven检查pom.xml里依赖的 scope 是不是provided改成默认或compile。4.4 页面 404Servlet 映射与项目上下文路径现象访问http://localhost:8080/login报 404但login.jsp能打开。原因Servlet 的WebServlet注解路径写错或者web.xml里没配servlet-mapping或者项目上下文路径不是/。解决检查WebServlet(/login)和表单action是否一致。如果用了web.xml配置确认url-pattern和访问路径匹配。IDEA 的 Tomcat 配置里 Deployment 的 Application context 默认是项目名_war_exploded访问时要带上这个前缀。可以在 Deployment 里把 Application context 改成/这样直接http://localhost:8080/login就能访问。4.5 订单提交后库存没变事务未提交或 SQL 条件不满足现象点击结算后订单生成了但商品库存还是原数。原因conn.setAutoCommit(false)后忘了commit()或者扣库存的 SQL 条件stock ?没满足executeUpdate返回 0 但没抛异常。解决在 Service 方法最后确认调用了conn.commit()。扣库存时判断返回值如果为 0 就throw new SQLException触发回滚。另外检查是不是在 DAO 层每次操作都拿了新连接导致事务不在同一个 Connection 上——事务必须用同一个连接。5. 让毕设项目更耐问从能跑到能讲清楚的三个进阶技巧5.1 用分页查询把商品列表做成可演示的规模初始数据只有几条演示时看不出分页效果。我一般会写一个批量插入脚本生成几百条商品数据然后在 DAO 层加分页。-- 批量生成测试商品 DELIMITER $$ CREATE PROCEDURE batch_insert_product() BEGIN DECLARE i INT DEFAULT 1; WHILE i 500 DO INSERT INTO product (name, category_id, price, stock, unit) VALUES (CONCAT(测试商品, i), FLOOR(1 RAND() * 5), ROUND(RAND() * 100, 2), FLOOR(RAND() * 500), 件); SET i i 1; END WHILE; END$$ DELIMITER ; CALL batch_insert_product();分页 SQL 用LIMITpublic ListProduct findByPage(int page, int pageSize) { ListProduct list new ArrayList(); String sql SELECT p.*, c.name AS categoryName FROM product p LEFT JOIN category c ON p.category_id c.id ORDER BY p.id DESC LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, (page - 1) * pageSize); ps.setInt(2, pageSize); ResultSet rs ps.executeQuery(); // ... 映射结果 } catch (SQLException e) { e.printStackTrace(); } return list; }参数page从 1 开始pageSize一般设 10 或 20。JSP 里加页码导航算总页数需要再写一个count查询。这样演示时能翻页答辩时也能讲清楚分页原理。5.2 用拦截器记录操作日志答辩时多一个亮点在 Filter 里加日志记录把用户操作写进operation_log表。代码量不大但能让老师看到你有安全意识。// LogFilter.java 片段 WebFilter(/product/*) public class LogFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; User user (User) request.getSession().getAttribute(currentUser); if (user ! null) { String action request.getRequestURI() ? (request.getQueryString() null ? : request.getQueryString()); logService.save(user.getId(), action, new Date()); } chain.doFilter(req, resp); } }operation_log表就三个字段user_id、action、create_time。答辩时可以说「系统对关键操作做了日志留痕便于追溯」比单纯说「我做了个增删改查」要有分量。5.3 数据库脚本的版本管理用 Flyway 思路整理 SQL 文件毕设项目往往改着改着数据库就乱了今天加个字段明天删个表最后脚本和代码对不上。我一般会按版本命名 SQL 文件V1__init.sql、V2__add_log_table.sql、V3__add_index.sql。虽然不引入 Flyway 框架但手动按顺序执行能保证从零建库时不会漏。# 按顺序导入 mysql -uroot -p supermarket V1__init.sql mysql -uroot -p supermarket V2__add_log_table.sql mysql -uroot -p supermarket V3__add_index.sql每个文件只做一件事文件头写清楚变更说明。这样即使换了电脑也能按顺序把数据库恢复到最新状态。答辩前把最终版脚本合并成一个supermarket_final.sql方便老师查看。5.4 我踩过的最大一个坑别在最后一刻改数据库密码说个血泪经验。有一次帮朋友调项目答辩前一天他改了 MySQL 密码忘了改DBUtil.java里的连接密码结果第二天演示时登录页直接 500。老师看着报错页面他当场懵了。后来我养成了一个习惯项目定稿后把所有配置项集中到一个db.properties文件里代码只读配置不改硬编码。# db.properties jdbc.urljdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456 jdbc.drivercom.mysql.cj.jdbc.Driver// DBUtil.java 读取配置 static { Properties props new Properties(); try (InputStream in DBUtil.class.getClassLoader().getResourceAsStream(db.properties)) { props.load(in); URL props.getProperty(jdbc.url); USER props.getProperty(jdbc.username); PASSWORD props.getProperty(jdbc.password); DRIVER props.getProperty(jdbc.driver); Class.forName(DRIVER); } catch (Exception e) { e.printStackTrace(); } }这样改密码只动一个文件不用满项目搜password。答辩前把db.properties里的密码改成你本机实际密码跑一遍全流程确认没问题再封版。这个习惯让我后来做任何项目都少了很多「最后一刻翻车」的后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表