资讯动态

超市订单管理系统JavaWeb课设:从建表到事务提交的完整实现

发布时间:2026/9/13 3:08:14 来源:尧图企业网站定制
简介这份基于Javaweb的超市订单管理系统是一套完成度较高的课程设计源码包适合JavaWeb初学者、在校生以及需要快速完成课设或答辩演示的开发者。项目涵盖商品、供应商、用户、订单等核心管理模块配合数据库脚本可直接运行能够帮助理解Servlet、JSP、JDBC以及前端页面交互的典型开发流程。压缩包共135个文件包含24个JSP页面、24个Java类、25个JavaScript文件、9个CSS样式文件以及SQL数据库脚本等前后端代码结构清晰便于按模块查阅和二次开发。资源大小约2.69MB轻量易用下载后解压即可导入IDE与数据库进行部署。目前已有280人学习下载适合作为JavaWeb课程设计参考或项目练手素材。源码中保留了完整的页面样式与基础交互搭配数据库初始化脚本能够节省环境搭建和基础功能开发的时间帮助读者快速跑通项目并在此基础上扩展功能。1. 这是“能跑”和“95分以上”的分水岭超市订单管理系统是 Javaweb 课设里出现频率最高的题目之一你看到“源码数据库”这种打包方式说明目标是把环境配好、导数据、部署就能演示。但 90 分看功能95 分以上看的是数据模型有没有设计对、JDBC 资源有没有关干净、下单是不是真的放进一个事务里。这篇文章顺着一个典型的超市订单管理系统 Javaweb 工程展开先讲订单和商品怎么建表再讲 DAO、Servlet、JSP 之间的职责划分然后给你一条从 zip 到 Tomcat 跑起来的完整路径。适合正在做数据库课程设计、或者打算拿现成源码改成自己课设的人也适合想搞清楚“为什么老师只看一眼就给了高分”的人。2. 超市订单管理系统的数据模型订单主表和明细表为什么必须拆开2.1 先想清楚业务实体再写 CREATE TABLE一个超市订单管理系统最常见的最小业务闭环是用户登录查看商品给某个供应商的商品下单生成订单扣减库存。哪怕是最简单的课设也需要四个基础实体用户、供应商、商品、订单。很多低分项目把订单设计成一张宽表把商品信息、供应商名称都冗余进来最后数据一团乱。正确做法是把订单拆成“订单主表”和“订单明细表”因为一张订单可能包含多个商品只有拆开才能满足 1:N 的建模要求。下面的建表语句基于 MySQL 8发动机用 InnoDB字符集统一 utf8mb4这是避免中文乱码的第一道防线。密码字段没有直接写成密码长度 20而是预留 64 位方便你在答辩前把它升级成加盐哈希。CREATE TABLE sys_user ( id int NOT NULL AUTO_INCREMENT, user_code varchar(40) NOT NULL, user_name varchar(60) NOT NULL, password varchar(64) NOT NULL, role tinyint DEFAULT 1 COMMENT 0:管理员 1:普通用户, PRIMARY KEY (id), UNIQUE KEY uk_user_code (user_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE provider ( id int NOT NULL AUTO_INCREMENT, pro_code varchar(40) NOT NULL, pro_name varchar(80) NOT NULL, contact varchar(40), phone varchar(20), PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE product ( id int NOT NULL AUTO_INCREMENT, pro_code varchar(40) NOT NULL, product_name varchar(80) NOT NULL, provider_id int, unit_price decimal(10,2) NOT NULL, quantity int DEFAULT 0, PRIMARY KEY (id), KEY idx_provider (provider_id), CONSTRAINT fk_product_provider FOREIGN KEY (provider_id) REFERENCES provider (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这三张表先把基础档案立住。供应商和商品之间是外键关系如果你不想用物理外键至少要在建表时留出列并在 code review 时说明逻辑外键的存在。商品字段里的provider_id是关键关联缺了它后面写“按供应商查商品”的多表查询会非常难受。2.2 订单表拆成 bill 和 bill_detail并在明细里冗余商品价格订单主表只需要记录这次订单的公共信息订单编号、下单人、下单时间、总金额、状态。具体买了哪几个商品、每个多少钱放进订单明细表。把商品单价冗余进明细表是一种刻意设计如果以后商品单价改了历史订单里的价格不会跟着变这比回表查商品表更稳定也更接近真实收银系统的做法。CREATE TABLE bill ( id int NOT NULL AUTO_INCREMENT, bill_code varchar(32) NOT NULL, created_by int NOT NULL, create_time datetime NOT NULL, total_amount decimal(10,2) DEFAULT 0, status tinyint DEFAULT 0 COMMENT 0:未支付 1:已支付 2:已取消, PRIMARY KEY (id), UNIQUE KEY uk_bill_code (bill_code), CONSTRAINT fk_bill_created_by FOREIGN KEY (created_by) REFERENCES sys_user (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE bill_detail ( id int NOT NULL AUTO_INCREMENT, bill_id int NOT NULL, product_id int NOT NULL, price decimal(10,2) NOT NULL COMMENT 下单时商品单价快照, quantity int NOT NULL, PRIMARY KEY (id), KEY idx_bill (bill_id), CONSTRAINT fk_detail_bill FOREIGN KEY (bill_id) REFERENCES bill (id), CONSTRAINT fk_detail_product FOREIGN KEY (product_id) REFERENCES product (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;表名关键字段作用sys_useruser_code, password, role登录认证与角色控制providerpro_code, pro_name供应商档案productprovider_id, unit_price, quantity商品信息与库存billbill_code, created_by, total_amount订单主表存公共信息bill_detailbill_id, product_id, price, quantity订单明细记录商品快照注意bill_total_amount这种字段不要靠 Java 程序里手动累加应该用 SQL 聚合算出后在前端展示。如果你后续要改表结构比如给bill加一个pay_time字段用 ALTER TABLE 在原有基础上改不要动不动删库重建数据库课程设计里最忌讳的是答辩时数据库里没有历史数据。3. 从 JDBC 到 Servlet 再到 JSP按职责分层的增删改查实现3.1 先写一个 DBUtil把驱动加载和连接管理收口很多低分源码把DriverManager.getConnection写在每一个 DAO 方法里一旦连接参数改起来就要全局替换。更常见的做法是写一个 DBUtil 工具类专门负责加载驱动和返回数据库连接。基于标题里的“源码数据库”结构项目一般不是 Maven 多模块工程而是标准的 Eclipse/IDEA Web 工程所以连接信息可以放在 DBUtil 的静态常量里也可以放到jdbc.properties我一般放在常量里课设阶段更好讲。package com.example.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/supermarket ?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(请检查 mysql-connector-j 驱动是否已放进 WEB-INF/lib); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这段代码里有三个参数必须和你的 MySQL 环境对上URL里的数据库名supermarketUSER和PASSWORD分别是 MySQL 登录名和密码。类名上方的static块保证驱动类只注册一次这就是“只做初始化不重复加载”的意思。3.2 用 PreparedStatement 做条件查询而不是字符串拼接假设你要实现“订单列表页根据订单编号模糊查询”DAO 层最常见、最稳妥的写法是使用PreparedStatement因为?占位符能避免 SQL 注入同时执行计划复用也对高并发更友好。下面以订单主表加用户名联合查询为例public ListBill findBills(String keyword) throws SQLException { String sql SELECT b.id, b.bill_code, b.create_time, b.total_amount, b.status, u.user_name FROM bill b JOIN sys_user u ON b.created_by u.id WHERE b.bill_code LIKE ? ORDER BY b.create_time DESC; ArrayListBill list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Bill b new Bill(); b.setId(rs.getInt(id)); b.setBillCode(rs.getString(bill_code)); b.setCreateTime(rs.getTimestamp(create_time)); b.setTotalAmount(rs.getBigDecimal(total_amount)); b.setStatus(rs.getInt(status)); b.setCreatedByName(rs.getString(user_name)); list.add(b); } } } return list; }这里ps.setString(1, % keyword %)是把通配符放到了参数里SQL 本身保持不变。注意整个数据访问逻辑全部放在 try-with-resources 里这样不管查询成功还是抛异常Connection、PreparedStatement、ResultSet 都会自动关闭不需要在 finally 里手写close()。3.3 Servlet 只负责取参数、调服务、跳页面在 Javaweb 课设里,用原生 Servlet 依然是主流。Servlet 里不应该出现 SQL 语句更不应该出现PrintWriter输出一段 HTML 给你的用户看。规范化做法是WebServlet(/bill/list)绑定访问路径在doGet里接收查询参数调用 DAO 拿到列表再存进 request 域并转发。WebServlet(/bill/list) public class BillListServlet extends HttpServlet { private BillDao billDao new BillDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String keyword req.getParameter(keyword); try { ListBill billList billDao.findBills(keyword); req.setAttribute(billList, billList); req.getRequestDispatcher(/jsp/billList.jsp).forward(req, resp); } catch (SQLException e) { e.printStackTrace(); resp.sendError(500, 订单查询失败); } } }参数keyword是从查询串bill/list?keywordxxx来的没有值时为 nullDAO 里的LIKE ?在传入 null 时需要单独处理否则会查询出空结果。这里的请求转发用/jsp/billList.jsp路径从 Web 根目录开始前面要加斜杠。3.4 JSP 循环渲染订单表格JSP 页面只做展示用 JSTL 的c:forEach循环输出列表是最容易读懂的做法。下面是 billList.jsp 的核心片段table border1 tr th订单编号/th th下单人/th th创建时间/th th总金额/th th状态/th /tr c:forEach items${billList} varbill tr td${bill.billCode}/td td${bill.createdByName}/td tdfmt:formatDate value${bill.createTime} patternyyyy-MM-dd HH:mm:ss//td td${bill.totalAmount}/td td${bill.status 1 ? 已支付 : 未支付}/td /tr /c:forEach /table页面里没有import java.sql.*也不会出现 JDBC 驱动类这正是分层的效果。到这里一个基于 Javaweb 的超市订单管理系统的核心链路——建表、DAO、Servlet、JSP——已经完整下一步是把整个项目从 zip 落地到 Tomcat 里跑起来。4. 从 zip 到 Tomcat 跑通 Javaweb 超市订单管理系统的完整配置4.1 先还原项目目录结构一个合格的课设源码解压后至少应该是这个结构否则说明打包的人没有按标准 Web 工程导出路径内容src/com/example/util/DBUtil.java数据库连接工具src/com/example/dao/*.javaDAO 层接口与实现src/com/example/servlet/*.javaServlet 控制器WebContent/jsp/*.jsp视图页面WebContent/WEB-INF/web.xml应用配置文件WebContent/WEB-INF/lib/*.jarMySQL 驱动、JSTL 标签库db/init.sql建库建表及初始化数据README.txt数据库账号、Tomcat 版本的说明如果打开的 zip 里没有 db 目录只有项目代码那你需要根据DBUtil中的 URL 推断数据库名再手动建库建表。这也是为什么老师看到“源码数据库”会觉得项目完整——数据库脚本本身就是验收物的一部分。4.2 导入数据库脚本打开终端进到 db 目录执行下面这行命令把表结构和初始化数据全部导入 MySQL。这里的-p参数会让你输入密码密码是安装 MySQL 时设置 root 用户的密码。mysql -u root -p db/init.sql执行完后用下面的登录命令检查表是否创建成功。如果出现ERROR 1049 (42000): Unknown database supermarket说明脚本里没有建库语句或者没有执行成功需要手动先CREATE DATABASE supermarket DEFAULT CHARACTER SET utf8mb4;再导入。mysql -u root -p -e use supermarket; show tables;能看到bill、bill_detail、product、provider、sys_user五张表数据库这步就算完成了。如果你想调整表结构比如给 product 表增加一个sale_price字段用 ALTER TABLE 语句去改而不是直接修改 init.sql 再重新导入否则你的历史数据会丢失。4.3 把工程导入 IDEA 并适配 Tomcat我一般建议把整个解压目录作为文件夹直接导入 IntelliJ IDEA然后打开 Project Structure确认 Artifacts 的类型是 Web Application Exploded。接着在 Tomcat Configuration 里把 Deployment 加上该 artifactApplication context 填写/supermarket。这里最容易踩的坑是WEB-INF/lib下少了mysql-connector-j和jstl两个 jar如果启动后报 ClassNotFoundException先检查这两个 jar 是否真实存在于输出目录。如果你用的是 Eclipse操作差不多File - Import - Existing Projects into Workspace然后把项目关联到本机 Tomcat右键 Run As - Run on Server 就能访问。注意 Tomcat 9 需要 JDK 8 以上Tomcat 10 的javax.servlet包名变更比较大老课设源码优先选 Tomcat 9。我处理过不少这种课设源码最常见的现象是改好数据库密码后访问首页一直是 404于是认为源码有 bug。实际上问题出在index.jsp没有放在WebContent根目录或没有配置默认欢迎页。此时检查web.xmlwelcome-file-list welcome-filejsp/login.jsp/welcome-file /welcome-file-list没有这段配置时Tomcat 默认去找/index.jsp如果找不到就显示目录列表 403。加上之后浏览器直接访问http://localhost:8080/supermarket/就会自动落到登录页。5. 让超市订单管理系统从“能演示”变成“95分以上”的排错与提分点5.1 本地开发的 5 个高频异常对照表课设阶段时间最紧张的反而不是写功能而是排错。下面这张表是我在帮人看 Javaweb 源码时经常遇到的组合对照排查基本能覆盖 90% 的启动和访问问题异常现象根本原因解决方式ClassNotFoundException: com.mysql.cj.jdbc.DriverMySQL 驱动 jar 缺失或版本过旧下载 mysql-connector-j 8.0 放到 WebContent/WEB-INF/libSQLException: Access denied for user rootlocalhostDBUtil 密码与 MySQL 实际密码不一致检查 DBUtil 静态常量或改成读取 jdbc.propertiesSQLException: Unknown databaseURL 里的库名不对把数据库名改成 init.sql 里 CREATE DATABASE 的名称java.sql.SQLException: The server time zone value is unrecognizedMySQL 8 时区信息没有配置URL 尾部追加 serverTimezoneAsia/Shanghai页面中文显示为问号页面编码、连接编码、表字符集至少一处不一致统一 UTF-8并在 web.xml 增加 CharacterEncodingFilter其中时区问题是最经典的坑。MySQL 8 默认使用CST但这个时区值在 JDBC 驱动里可能无法解析。最稳妥的做法是写到 URL 里也就是前面 DBUtil 里的serverTimezoneAsia/Shanghai这行不要删。5.2 下单和扣库存必须放进同一个事务里90 分的项目点击“确认下单”后会刷新出订单列表但商品表的库存没有变化或者库存扣了但没有订单记录。这种问题根源是 JDBC 的自动提交模式每条 SQL 执行完立即 commit中间如果出现异常只能留下残缺数据。把下单、写明细、扣库存这三个动作包进一个事务是拉到 95 分的重要加分项。public void createOrder(Bill bill, ListBillDetail details) { try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); try { // 1. 插入订单主表拿到自增 ID int billId insertBill(conn, bill); // 2. 插入所有订单明细 for (BillDetail detail : details) { detail.setBillId(billId); insertBillDetail(conn, detail); } // 3. 更新库存库存不足时应该抛出异常 boolean ok updateStock(conn, details); if (!ok) { throw new RuntimeException(库存不足); } conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } } catch (SQLException e) { throw new RuntimeException(订单创建失败, e); } }关键点在于setAutoCommit(false)之后任何一个 insert 或 update 失败块会执行conn.rollback()。insertBill内部要用PreparedStatement.RETURN_GENERATED_KEYS才能拿到自增 id否则明细表无法关联主表。扣库存的 SQL 要写成“带条件更新”UPDATE product SET quantity quantity - ? WHERE id ? AND quantity ?如果返回的受影响行数为 0说明库存不足事务滚回。这一行 SQL 同时完成了扣库存和超卖校验是事务里最有含金量的部分。5.3 列表页改成分页查询SQL 用 LIMIT 而不是程序截断低分项目经常是一次性把整个表的数据全部查出来在 JSP 里用 List 的 subList 切页。数据量一旦超过几千条页面会明显卡顿。正确做法是分页查询数据库只返回当前需要的一页数据。SELECT bill_code, create_time, total_amount, status FROM bill ORDER BY create_time DESC LIMIT ?, ?;Java 端对应两个参数第一个?是起始行偏移量从 0 开始第二个?是每页条数。常见的错误是让用户输入页码然后计算出(page - 1) * pageSize但没有考虑总页数所以一定要先执行SELECT COUNT(*)拿总数。配合搜索条件时COUNT 语句和列表语句的 WHERE 条件要完全一致。6. 用一次“刻意验证”让答辩老师觉得你做了测试项目做完后别急着打包先在本地做一次成体系的验证。看到一个 95 分以上课设项目的论文或者答辩演示老师最信任的不是页面截图而是你能当场证明“事务真的回滚了”。具体的做法是故意把库存改成 1再下两个同商品订单第二个订单提交后页面上出现“库存不足”提示然后去数据库里查 bill 表确认这个失败订单没有残留记录。这个验证过程可以写成一个小的测试入口类不需要集成 JUnit直接用 main 方法跑。public class DemoVerify { public static void main(String[] args) { BillServiceImpl service new BillServiceImpl(); Bill bill new Bill(); bill.setBillCode(NO20240518002); BillDetail detail new BillDetail(); detail.setProductId(3); detail.setPrice(new BigDecimal(5.00)); detail.setQuantity(10); service.createOrder(bill, Arrays.asList(detail)); System.out.println(订单已创建ID bill.getId()); } }代码本身没有难度但它证明了你可以脱离页面独立演示核心业务。在答辩现场要做的是三步先展示数据库里该商品当前库存量接着运行这段代码正常创建订单然后把库存改成 5把quantity改成 10再运行一次观察输出中的异常。这里bill.getId()只有在事务提交后才能拿到值所以这也是验证事务提交时机的好办法。最后的代码走查我会盯三个点所有 DAO 方法是否都在 try-with-resources 中关闭了资源createOrder是否把三个写操作包在了同一个事务里模糊查询是否用了PreparedStatement而不是字符串拼接。只要这三点通过了你的超市订单管理系统就不是网上随手下个源码交差而是一个有明确设计意图和验证过程的 Javaweb 数据库课程设计。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价