☰
Java课设超市订单管理系统源码解析:Servlet+JDBC实现与避坑指南
2026/10/8 7:37:16 网站建设 项目流程

简介:这是一套面向高校计算机专业学生的Java课程设计/实训项目源码,主题为超市订单管理系统,适合正在准备课设、实训答辩或需要Web开发练手项目的初学者与进阶学习者。项目采用原生JDBC连接MySQL数据库,基于Servlet与JSP构建Web工程,未依赖重型框架,便于理解底层请求处理与数据访问流程。压缩包共231个文件,约1.57MB,包含31个Java源文件、40个JSP页面、46个JavaScript脚本、10个CSS样式表及大量png、jpg图片资源,另有xml配置、properties属性文件与md说明文档,覆盖前端页面、后端逻辑与数据库配置各层。资源中已给出servlet-api、jsp-api与mysql-connector-java等依赖坐标,导入jar包后即可运行调试。目前已有1817人学习下载,可作为课程设计参考模板,帮助读者快速理清订单管理模块的目录结构与实现思路,并在此基础上完成二次开发与功能扩展。

1. 从一份超市订单管理源码说起:它到底能跑出什么

如果你正在找一份能直接跑起来、结构不绕、代码量适中的 Java Web 课程设计,这份「java课设超市订单管理系统源代码」大概率能省掉你从零搭架子的一周时间。它解决的不是高并发订单调度,也不是分布式事务,而是一个典型教学场景下的完整闭环:商品管理、订单录入、订单查询、用户登录、后台维护,基于 MySQL 数据库,用原生 JDBC 直连,Servlet + JSP 做前后端交互,前端还带了 datepicker 和 WdatePicker 这类日期控件样式。换句话说,它不是企业级项目,但它是一份「能讲清楚 MVC 分层、能演示 JDBC 增删改查、能当答辩材料」的实训级源码。

适合谁?第一类是大二大三刚学完 Java Web、需要交课程设计但不想抄网上一模一样模板的人;第二类是想复习 Servlet 生命周期、JDBC 连接池、JSP 页面传参的初级开发;第三类是要带学生做实训、需要一份可拆解案例的讲师。不适合谁?如果你在找 Spring Boot + MyBatis 的多商户商城,或者需要行级权限、分布式锁、消息队列的项目,这份源码会让你失望——它的技术栈停留在 Servlet 2.5 和 JSP 2.3.3 时代,连 Maven 依赖都写得比较原始。

我拆这份包的时候,第一反应是「这很课设」:public.css、style.css、datepicker.css 反复出现,说明前端样式是拼出来的,不是工程化构建;WdatePicker 是国产日期控件,说明订单日期筛选是核心交互之一。这些细节反而说明它真实——不是从 GitHub 上扒的英文项目改个名,而是国内课堂里长出来的东西。下面我从环境搭建、数据库设计、核心代码、避坑、进阶验证五个层面,把它拆到你能照着复现的程度。

2. 环境搭建与依赖导入:Servlet 2.5 + JDBC 的最小可跑组合

2.1 为什么这份源码不用 Maven 也能跑

摘要里给了 Maven 依赖片段,但正文只列了 CSS 文件,说明这个包大概率是「传统 Web 工程」结构:WebContent 或 WebRoot 目录下有 WEB-INF、jsp、css、js,lib 目录里塞 jar 包。很多课设源码都是这种形态,因为老师要求用 Eclipse 或 MyEclipse 直接导入,而不是让你配 pom.xml。常见做法是:如果你用 IDEA,可以新建一个 Java Enterprise 项目,把源码目录整体拷进去,再手动把 jar 包加到模块依赖里;如果你用 Eclipse,直接 File → Import → Existing Projects into Workspace,选解压后的根目录,它会自动识别 .project 文件。

这里有个选型理由要讲清楚:Servlet 2.5 对应的是 Tomcat 6/7,JSP 2.3.3 对应 Tomcat 7/8。如果你用 Tomcat 10,包名已经从 javax.servlet 变成 jakarta.servlet,这份源码会直接报 ClassNotFoundException。所以我的建议是:Tomcat 8.5 或 9.0,JDK 8,MySQL 5.7 或 8.0,这是最稳的组合。别用 JDK 17 配 Tomcat 10 去硬跑,你会花两小时在改包名上,而这份源码的价值不在迁移。

2.2 依赖导入的实操步骤

先确认你解压后的目录结构。典型课设包长这样:

超市订单管理系统/ ├── src/ # Java 源码 │ ├── com/supermarket/dao/ │ ├── com/supermarket/servlet/ │ └── com/supermarket/util/ ├── WebContent/ # 或 WebRoot │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ # jar 包放这里 │ ├── css/ │ │ ├── style.css │ │ ├── public.css │ │ ├── datepicker.css │ │ └── WdatePicker.css │ └── jsp/ └── database.sql # 建表脚本

如果 lib 目录里没有 jar,你需要手动补三个:servlet-api.jar、jsp-api.jar、mysql-connector-java.jar。注意 servlet-api 和 jsp-api 在 Tomcat 的 lib 目录里也有,但编译时需要显式加到项目 classpath,运行时由容器提供,所以打包时不要放进 WEB-INF/lib,否则可能冲突。mysql 驱动则必须放进 WEB-INF/lib,否则运行时报 No suitable driver。

<!-- 如果你坚持用 Maven,pom.xml 里这样写 --> <dependencies> <!-- servlet 编译期依赖,scope 必须是 provided --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <!-- jsp 同理,容器会提供 --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- mysql 驱动必须打进 war 包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> </dependencies>

参数说明:servlet-api 的 scope 写成 provided,意思是编译和测试时用,打包时不带进去,因为 Tomcat 自己有;mysql-connector-java 的版本选 5.1.x 还是 8.0.x,取决于你 MySQL 服务端版本。5.7 用 5.1.47 稳,8.0 用 8.0.28 以上,但驱动类名从 com.mysql.jdbc.Driver 变成 com.mysql.cj.jdbc.Driver,URL 还要加 serverTimezone=Asia/Shanghai。这份源码大概率写的是老驱动类名,所以你用 MySQL 5.7 最省事。

2.3 数据库连接配置在哪里改

原生 JDBC 项目通常有一个 DBUtil 或 BaseDao 类,里面硬编码了 URL、用户名、密码。你导入后第一件事就是全局搜索jdbc:mysql,把库名、账号、密码改成你本地的。常见写法:

// DBUtil.java 典型结构 public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/supermarket?useUnicode=true&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; public static Connection getConnection() throws SQLException { try { Class.forName("com.mysql.jdbc.Driver"); // 老驱动,MySQL 5.x 用 } catch (ClassNotFoundException e) { e.printStackTrace(); } return DriverManager.getConnection(URL, USER, PASSWORD); } }

逻辑说明:Class.forName 是显式加载驱动,JDBC 4.0 以后其实可以省略,但课设代码基本都保留着。URL 里的 useUnicode 和 characterEncoding 必须加,否则中文商品名会乱码。如果你用 MySQL 8.0,驱动类改成 com.mysql.cj.jdbc.Driver,URL 末尾加&serverTimezone=Asia/Shanghai&useSSL=false。改完先别急着启动 Tomcat,用 main 方法测一下 getConnection 能不能拿到连接,这一步能排掉一半的「启动就报错」。

3. 数据库表结构与订单业务逻辑:从建表到 JDBC 增删改查

3.1 核心表设计:订单、商品、用户三张主表

课设级超市订单系统,表不会太多,通常 4 到 6 张。我拆过的类似项目里,核心表是:user(用户/管理员)、product(商品)、orders(订单主表)、order_detail(订单明细)。有的还会加 category(分类)和 supplier(供应商)。建表脚本一般在 database.sql 里,导入时用source命令或 Navicat 直接跑。

-- 商品表:注意价格用 decimal,不要用 float CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, category_id INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单主表:订单号、用户、总金额、状态、下单时间 CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) UNIQUE NOT NULL, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status INT DEFAULT 0, -- 0 待付款 1 已付款 2 已发货 3 已完成 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单明细:一个订单多条明细,外键关联 orders CREATE TABLE order_detail ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL, unit_price DECIMAL(10,2) NOT NULL );

参数说明:price 和 total_amount 用 DECIMAL 而不是 FLOAT,是因为浮点数在金额计算时会出现 0.1 + 0.2 = 0.30000000000000004 这种玄学问题,答辩时被老师问一句「为什么金额对不上」就很尴尬。status 用 INT 而不是 ENUM,是为了 JDBC 映射方便,Java 里用 int 接收,前端用 if-else 显示中文状态。order_no 加 UNIQUE 索引,防止重复提交生成相同订单号。

3.2 JDBC 增删改查的典型写法与参数含义

这份源码用原生 JDBC,意味着没有 MyBatis 那种映射框架,所有 SQL 都写在 DAO 层。典型结构是:ProductDao、OrderDao、UserDao,每个类里有 add、delete、update、findById、findAll 方法。下面以订单插入为例,展示一个完整的事务写法:

// OrderDao.java 插入订单主表 + 明细表,必须用事务 public boolean addOrder(Orders order, List<OrderDetail> details) { Connection conn = null; PreparedStatement ps = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,开启事务 // 1. 插入订单主表 String sql = "INSERT INTO orders(order_no, user_id, total_amount, status) VALUES(?,?,?,?)"; ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, order.getOrderNo()); ps.setInt(2, order.getUserId()); ps.setBigDecimal(3, order.getTotalAmount()); ps.setInt(4, order.getStatus()); ps.executeUpdate(); // 2. 拿到自增主键,作为明细表的 order_id ResultSet rs = ps.getGeneratedKeys(); int orderId = 0; if (rs.next()) { orderId = rs.getInt(1); } // 3. 批量插入明细 String detailSql = "INSERT INTO order_detail(order_id, product_id, quantity, unit_price) VALUES(?,?,?,?)"; ps = conn.prepareStatement(detailSql); for (OrderDetail d : details) { ps.setInt(1, orderId); ps.setInt(2, d.getProductId()); ps.setInt(3, d.getQuantity()); ps.setBigDecimal(4, d.getUnitPrice()); ps.addBatch(); } ps.executeBatch(); conn.commit(); // 全部成功才提交 return true; } catch (Exception e) { try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { // 关闭资源,顺序:ResultSet → Statement → Connection try { if (ps != null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }

逻辑说明:这段代码的关键在setAutoCommit(false)和commit/rollback。订单主表和明细表必须同时成功,否则会出现「有订单没明细」的脏数据。Statement.RETURN_GENERATED_KEYS是告诉 JDBC 返回自增主键,这样明细表才能关联到正确的 order_id。addBatch和executeBatch是批量插入,比循环单条插入快,但课设数据量小,差别不明显,写出来是为了答辩时能讲。

参数怎么改:如果你要加库存扣减,在插入明细后加一条UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?,用ps.executeUpdate()的返回值判断是否扣减成功,返回 0 说明库存不足,直接抛异常触发 rollback。这是订单系统最核心的一致性逻辑,也是答辩老师最爱问的点。

3.3 Servlet 接收参数与 JSP 回显的链路

前端 JSP 提交表单到 Servlet,Servlet 调 DAO,再把结果存 request 域,转发回 JSP。典型链路:

// OrderServlet.java doPost 方法 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 必须放在 getParameter 之前 String action = request.getParameter("action"); if ("add".equals(action)) { String userId = request.getParameter("userId"); String[] productIds = request.getParameterValues("productId"); // 多选商品 String[] quantities = request.getParameterValues("quantity"); List<OrderDetail> details = new ArrayList<>(); for (int i = 0; i < productIds.length; i++) { OrderDetail d = new OrderDetail(); d.setProductId(Integer.parseInt(productIds[i])); d.setQuantity(Integer.parseInt(quantities[i])); details.add(d); } // 省略计算总金额、生成订单号 boolean ok = orderDao.addOrder(order, details); if (ok) { response.sendRedirect("order?action=list"); // 重定向防重复提交 } else { request.setAttribute("msg", "下单失败,请检查库存"); request.getRequestDispatcher("/jsp/error.jsp").forward(request, response); } } }

参数说明:request.setCharacterEncoding("UTF-8")必须写在所有 getParameter 之前,否则中文参数乱码。getParameterValues用于多选框,返回 String 数组。重定向用sendRedirect,转发用getRequestDispatcher().forward(),前者会改变浏览器地址栏,后者不会。下单成功后用重定向,是为了防止用户刷新页面重复提交订单,这是课设里容易被忽略但答辩会加分的细节。

4. 避坑与常见问题排查:从 404 到中文乱码的实战记录

4.1 启动后 404:web.xml 版本与 URL 映射对不上

现象:Tomcat 启动没报错,但访问http://localhost:8080/项目名/显示 404。原因通常是 web.xml 里 servlet 的 url-pattern 写的是/order,但你访问的路径少了项目名,或者 web.xml 的 version 声明是 2.5 但用了 3.0 的注解方式。解决:先看 Tomcat 控制台有没有「Deployment of web application archive」成功日志,再看 web.xml 里<url-pattern>的具体值。如果是注解@WebServlet("/order"),确认 web.xml 的metadata-complete="false"或者干脆删掉 web.xml 里的 servlet 配置。

4.2 中文乱码:POST 和 GET 要分开处理

现象:商品名显示成??????或测试。原因:POST 请求乱码是 request 编码问题,GET 请求乱码是 Tomcat 的 URIEncoding 问题。解决:POST 在 Servlet 第一行加request.setCharacterEncoding("UTF-8");GET 要么在 Tomcat 的 server.xml 里给 Connector 加URIEncoding="UTF-8",要么在代码里用new String(request.getParameter("name").getBytes("ISO-8859-1"), "UTF-8")手动转。数据库连接 URL 也要加useUnicode=true&characterEncoding=utf8,三层都对齐才不会乱。

4.3 日期控件不生效:WdatePicker 的路径与 onfocus 绑定

现象:订单查询页面的日期输入框点了没反应,或者报WdatePicker is not defined。原因:WdatePicker.js 没引入,或者引入路径不对。这份源码里 CSS 有 WdatePicker.css,说明 JS 也应该在。解决:在 JSP 的<head>里加<script src="${pageContext.request.contextPath}/js/WdatePicker.js"></script>,输入框写<input type="text" onfocus="WdatePicker({dateFmt:'yyyy-MM-dd'})" />。注意pageContext.request.contextPath是拿项目根路径,避免相对路径在转发后失效。

4.4 数据库连接报 Access denied:账号密码与权限

现象:java.sql.SQLException: Access denied for user 'root'@'localhost'。原因:DBUtil 里的密码和你 MySQL 实际密码不一致,或者 MySQL 8.0 的 root 默认需要caching_sha2_password认证而老驱动不支持。解决:先用命令行mysql -uroot -p确认密码能登录;如果 MySQL 8.0 报认证插件问题,执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';再刷新权限。驱动版本低于 8.0 时,连 MySQL 8.0 必须改认证插件,这是血泪经验。

4.5 订单金额对不上:BigDecimal 与 double 混用

现象:订单总金额显示 99.99999999 而不是 100.00。原因:DAO 里用 double 接收 DECIMAL 字段,或者 Java 里用+累加 double。解决:实体类金额字段用BigDecimal,累加用bigDecimal.add(other),比较用compareTo而不是equals。JSP 显示时用<fmt:formatNumber value="${order.totalAmount}" pattern="0.00"/>格式化。这个坑在答辩时被问到「为什么金额有误差」会很扣分,提前改掉。

5. 进阶验证与二次开发:怎么把课设讲成项目经验

5.1 用 Postman 验证 Servlet 接口是否独立可用

课设项目通常前后端耦合,JSP 直接嵌 Java 代码,不好测。你可以把 Servlet 当接口测:启动 Tomcat 后,用 Postman 发 POST 请求到http://localhost:8080/项目名/order,Body 选x-www-form-urlencoded,传action=add&userId=1&productId=1&quantity=2。如果返回重定向或错误页,说明 Servlet 逻辑通了。这一步的价值在于:面试时你可以说「我把订单模块的 Servlet 抽出来做了接口测试」,比「我做了个课设」有说服力。

5.2 加一个简单的库存校验,让订单逻辑闭环

原始课设可能只插订单不扣库存。你可以在 OrderDao.addOrder 里加库存校验,用事务保证一致性:

// 在插入明细前,先校验并扣减库存 String stockSql = "UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?"; ps = conn.prepareStatement(stockSql); for (OrderDetail d : details) { ps.setInt(1, d.getQuantity()); ps.setInt(2, d.getProductId()); ps.setInt(3, d.getQuantity()); int rows = ps.executeUpdate(); if (rows == 0) { throw new RuntimeException("库存不足,商品ID:" + d.getProductId()); } }

逻辑说明:stock >= ?是乐观锁思路,只有库存足够才更新,返回 0 说明库存不够,抛异常触发 rollback。这样订单和库存要么一起成功,要么一起回滚。参数怎么改:如果你要支持超卖,把条件改成stock >= 0就不对了,那是允许负库存;正确做法是加数据库行锁SELECT ... FOR UPDATE,但课设级别用 UPDATE 条件判断就够了。

5.3 用日志代替 System.out.println 排查 SQL 问题

原生 JDBC 项目里到处是e.printStackTrace(),排查时不知道哪条 SQL 出错。我一般会加一个简单的日志工具类,把 SQL 和参数打出来:

// LogUtil.java 简易日志 public class LogUtil { public static void logSql(String sql, Object... params) { StringBuilder sb = new StringBuilder(); sb.append("[SQL] ").append(sql); if (params != null && params.length > 0) { sb.append(" [PARAMS] "); for (Object p : params) { sb.append(p).append(", "); } } System.out.println(sb.toString()); } }

在 DAO 里ps.executeUpdate()之前调用LogUtil.logSql(sql, order.getOrderNo(), order.getUserId()),控制台就能看到实际执行的 SQL 和参数。这个习惯我从课设一直带到工作,后来用 MyBatis 的log4j也是同样思路。从那以后我每次导入陌生 JDBC 项目,都先加日志再跑,不然报错信息只有一行SQLException,根本不知道是哪条语句翻车。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询