☰
Java Web网上订餐系统源码部署:从JSP+Servlet+MySQL到环境避坑全指南
2026/10/26 17:36:10 网站建设 项目流程

简介:一套基于Java、JSP与MySQL的网上订餐系统源码,面向Java Web学习者、毕业设计选题者及需要快速搭建订餐演示项目的开发者。工程完整覆盖JSP视图、Servlet控制与MySQL数据三大层,体现MVC设计模式,含数据库初始化脚本、IDE工程配置与WebContent静态资源,可清晰理解从页面提交订餐请求到后端处理并持久化的完整流程。压缩包共1262个文件、47.38MB,以css和png静态资源为主,另有48个JSP页面、44个Java类、42个Jar依赖包及SQL脚本,结构规整便于按模块阅读。已有385人学习下载,适合课程设计或Java Web入门实战,可直接导入Eclipse或IntelliJ IDEA运行调试,结合源码掌握JDBC操作、请求转发与订单状态管理等关键实现。

1. 网上订餐系统源码包到手:先摸清版本组合,再谈跑起来

同样是手里一个 zip 包,有人两小时把“基于 java+jsp+mysql 简单网上订餐系统源码.zip”跑了起来,有人折腾三天还停在登录页报错。区别往往不在代码水平,而在有没有按 Java Web 老项目的节奏来:先定 JDK 和 Tomcat 版本,再看 MySQL 与驱动是否匹配,最后才谈改代码。这个项目的业务并不复杂——用户注册登录、菜品列表、加入购物车、提交订单,正好覆盖了 JSP+Servlet+JDBC 最典型的一条学习链路,对做毕设、课设,以及刚入行想补 Java Web 功底的开发者来说都值得完整走一遍。本文按我接手这类老项目时的实际操作顺序展开:先认工程结构,再读关键代码,然后搭环境部署,最后是跑不起来时最值得查的几个现场。

2. 一上来别急着改代码:源码目录结构与版本配对是第一步

2.1 解压后常见的工程目录:src、WebRoot、WEB-INF 各管什么

用 IDE 打开之前,先在资源管理器里把这个 zip 解开,确认目录结构是否完整。这类基于 JSP + Servlet 的老项目,常见结构是下面这样:

order-system/ ├── src/ # Java 源码:Servlet、DAO、Model │ ├── com/example/dao/ │ ├── com/example/model/ │ ├── com/example.servlet/ │ └── com/example.util/ ├── WebRoot/ # 也常叫 webapp 或 WebContent │ ├── css/ │ ├── js/ │ ├── images/ │ ├── WEB-INF/ │ │ ├── web.xml # Servlet 映射、欢迎页、过滤器配置 │ │ └── lib/ # mysql-connector-java.jar 等依赖 │ ├── login.jsp │ ├── register.jsp │ ├── index.jsp # 菜品列表页 │ ├── cart.jsp │ └── order.jsp └── db.sql # 建库建表脚本,有的包会放在 doc 目录下

src下面放的是 Java 源码,其中 DAO 类负责操作 MySQL,Servlet 类接收 JSP 页面提交的请求,Model 类对应数据库里的用户、菜品、订单等实体。WebRoot是 Web 应用的根目录,所有 JSP 页面和静态资源都放在这里;容器部署时指向的就是这个目录。WEB-INF是 Web 容器不允许浏览器直接访问的目录,web.xml里配置了 Servlet 的 URL 映射,lib下放着项目运行所必需的 jar 包——如果缺了 mysql 驱动 jar,后面必然会出现ClassNotFoundException。

先花五分钟把这几块对上号,比直接打开 IDE 看报错要省时间。常见做法是先用tree命令或在资源管理器里大概扫一眼,确认WebRoot/WEB-INF/web.xml和src目录都在。如果解压出来只有一堆 JSP 文件而没有 Java 源码,那说明 zip 里可能只放了页面部分,需要回头找完整包。

2.2 版本搭配参考:JDK 8 + Tomcat 8/9 + MySQL 5.7/8.0 为什么稳

这类老源码对运行环境极其敏感,我一般建议直接采用下面这套组合,它覆盖了绝大多数课设和毕设项目的兼容范围:

组件推荐版本理由
JDK1.8老代码基于 Java 8 语法编写,切换到 JDK 11+ 可能出现模块化或依赖兼容问题
Tomcat8.5 或 9.x使用javax.servlet命名空间,和老源码完全兼容
MySQL5.7 或 8.05.7 兼容性最好,8.0 需要额外处理时区和 SSL 参数
JDBC 驱动mysql-connector-java 5.1.49 或 8.0.x驱动版本必须和 MySQL 版本匹配,不能混用

JDK 8 到现在仍是 Java Web 老项目的“安全牌”,Tomcat 8.5/9 默认支持javax.servlet包,源码里import javax.servlet.http.HttpServlet这类语句不需要改动。如果拿到包以后直接用了 Tomcat 10,启动时会发现所有和 Servlet 相关的类都找不到,这是因为 Tomcat 10 把命名空间换成了jakarta.servlet。这个问题在避坑章节里会专门展开。

MySQL 选 5.7 还是 8.0,主要取决于本机已经装了什么。我的习惯是如果本机是 MySQL 8.0,就不用为了老项目特意重装 5.7,只要保证驱动用 8.0.x,并且在 JDBC 连接串里把serverTimezone配好即可。反过来,如果只有 MySQL 5.7,用 8.0.x 的驱动也能连,不会有什么问题。最容易翻车的组合是 MySQL 8.0 配 mysql-connector-java 5.1.0 这类老驱动,后面连接数据库时会因为时区问题抛异常。

3. 拆核心代码:用户、菜品、订单三块业务背后的 JSP+Servlet+JDBC 链路

3.1 建库建表 SQL:四张表把用户、菜品、订单与订单明细一次立住

网上订餐系统的业务不复杂,数据库层面通常只需要四张表:用户表、菜品表、订单主表和订单明细表。购物车在简单项目里往往不建表,直接用 JSP 的 Session 对象保存。下面这套建表 SQL 是我按这类项目的常见需求整理的,可以直接在 Navicat 或 MySQL 命令行执行:

-- 如果库不存在则创建 CREATE DATABASE IF NOT EXISTS order_system DEFAULT CHARACTER SET utf8mb4; USE order_system; -- 用户表 CREATE TABLE IF NOT EXISTS `user` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL COMMENT '登录名', `password` VARCHAR(50) NOT NULL COMMENT '密码,课设项目常为明文存储', `phone` VARCHAR(11) DEFAULT NULL COMMENT '手机号', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 菜品表 CREATE TABLE IF NOT EXISTS `food` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL COMMENT '菜品名', `price` DECIMAL(10,2) NOT NULL COMMENT '单价', `image` VARCHAR(200) DEFAULT NULL COMMENT '图片路径', `description` VARCHAR(255) DEFAULT NULL COMMENT '描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 订单主表 CREATE TABLE IF NOT EXISTS `orders` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `user_id` INT(11) NOT NULL COMMENT '下单用户', `total_price` DECIMAL(10,2) NOT NULL COMMENT '总金额', `status` VARCHAR(20) NOT NULL DEFAULT '待确认', `create_time` DATETIME NOT NULL COMMENT '下单时间', PRIMARY KEY (`id`), KEY `idx_user` (`user_id`), CONSTRAINT `fk_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 订单明细表 CREATE TABLE IF NOT EXISTS `order_item` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `order_id` INT(11) NOT NULL, `food_id` INT(11) NOT NULL, `food_name` VARCHAR(50) NOT NULL COMMENT '下单时的菜品名快照', `price` DECIMAL(10,2) NOT NULL COMMENT '下单时单价快照', `count` INT(11) NOT NULL COMMENT '数量', PRIMARY KEY (`id`), KEY `idx_order` (`order_id`), CONSTRAINT `fk_order` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 初始化几条菜品数据,方便页面展示 INSERT INTO `food` (`name`, `price`, `description`) VALUES ('鱼香肉丝', 18.00, '经典川菜'), ('宫保鸡丁', 22.00, '微辣下饭'), ('番茄炒蛋', 12.00, '家常口味');

这里有几个细节值得注意。所有表都用InnoDB引擎,因为下单时要在订单主表和明细表之间做事务控制,MyISAM 不支持事务。字符集统一用utf8mb4,比utf8覆盖范围更广,能存下 emoji 和一些生僻字,也避免菜品名称出现乱码。order_item表里特意冗余了food_name和price两个字段,这是为了防止菜品价格或名称日后被修改而导致历史订单对不上账——订单明细必须要存“下单那一刻”的快照,而不是下单后再去关联菜品表查询。

3.2 JDBC 连接与登录查询:换环境后必调的三个参数

建完表,接下来看 Java 侧怎么连 MySQL。这类项目通常有一个工具类负责创建数据库连接,常见名字是DBUtil.java或JdbcUtil.java。核心代码大致如下:

package com.example.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { // 旧项目常见写法:把连接参数直接写在常量里 private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; // serverTimezone 是 MySQL 8.0 之后必须要加的参数 private static final String URL = "jdbc:mysql://localhost:3306/order_system" + "?useUnicode=true&characterEncoding=utf8" + "&serverTimezone=Asia/Shanghai&useSSL=false"; private static final String USERNAME = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } }

这段代码里有三个参数在换环境后必须检查。第一个是DRIVER:MySQL 5.7 时代常用com.mysql.jdbc.Driver,MySQL 8.0 之后官方驱动改成了com.mysql.cj.jdbc.Driver,如果写的是老驱动名,Class.forName 会直接报类找不到。第二个是URL里的serverTimezone=Asia/Shanghai——MySQL 8.0 的驱动强制要求指定时区,不加就抛The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,这是老项目迁移到新 MySQL 最常见的翻车点。第三个是useSSL=false,MySQL 8.0 默认开了 SSL 协商,本地开发环境没必要走 SSL,不加这个参数有时会报 SSL 连接警告甚至失败。

连接拿到手后,登录功能的 DAO 层一般长这样,用PreparedStatement执行带参数的查询:

public User findUserByUsernameAndPassword(String username, String password) { String sql = "SELECT id, username, phone FROM user WHERE username = ? AND password = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setPhone(rs.getString("phone")); return user; } } } catch (SQLException e) { e.printStackTrace(); } return null; }

PreparedStatement的好处有两个:一是参数用?占位符,通过setString传入,避免了手工拼接 SQL 字符串带来的注入风险;二是同一结构的 SQL 可以被数据库预编译,循环执行时性能更好。登录这里有个容易被忽略的点:password在简单项目里直接存明文,查询时也是明文比对。生产环境绝不能这么写,至少要加盐后做 SHA-256 哈希。但课设项目为了演示方便,普遍是明文,接手后如果要做成作品集,建议至少把注册逻辑改成 MD5 存储。

3.3 下单要过事务:购物车从 Session 落到订单表的核心代码

购物车在简单网上订餐系统里一般不单独建表,页面把选中的菜品放在 Session 的一个 Map 里,Map<Food, Integer>的 key 是菜品对象,value 是数量。用户点击“提交订单”时,后端要把这个 Map 拆成一条订单主记录和若干订单明细记录,而且必须保证它们同时成功、同时失败——否则就会出现“订单主表有记录,明细却是空的”这种脏数据。看下面的下单方法:

public boolean createOrder(User user, Map<Food, Integer> cart) { Connection conn = null; PreparedStatement ps = null; ResultSet keys = null; boolean success = false; try { conn = DBUtil.getConnection(); // 关键点 1:关闭自动提交,手动控制事务 conn.setAutoCommit(false); // 先算总价 double total = 0.0; for (Map.Entry<Food, Integer> entry : cart.entrySet()) { total += entry.getKey().getPrice() * entry.getValue(); } // 插入订单主表,并拿到自增主键 String insertOrder = "INSERT INTO orders(user_id, total_price, status, create_time) " + "VALUES(?, ?, '待确认', NOW())"; ps = conn.prepareStatement(insertOrder, Statement.RETURN_GENERATED_KEYS); ps.setInt(1, user.getId()); ps.setDouble(2, total); ps.executeUpdate(); keys = ps.getGeneratedKeys(); int orderId = 0; if (keys.next()) { orderId = keys.getInt(1); } // 批量插入订单明细 String insertItem = "INSERT INTO order_item(order_id, food_id, food_name, price, count) " + "VALUES(?, ?, ?, ?, ?)"; ps = conn.prepareStatement(insertItem); for (Map.Entry<Food, Integer> entry : cart.entrySet()) { Food food = entry.getKey(); ps.setInt(1, orderId); ps.setInt(2, food.getId()); ps.setString(3, food.getName()); ps.setDouble(4, food.getPrice()); ps.setInt(5, entry.getValue()); ps.addBatch(); // 暂存到批处理 } ps.executeBatch(); // 关键点 2:一次性提交所有明细 conn.commit(); // 关键点 3:全部成功才提交 success = true; } catch (Exception e) { try { if (conn != null) { conn.rollback(); // 任何一个环节失败,全部回滚 } } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); } finally { // 实际项目中要依次关闭 ResultSet、PreparedStatement、Connection // 建议用 try-with-resources 自动关闭 } return success; }

这段代码把事务控制的三个关键点全占了:setAutoCommit(false)告诉 MySQL 先不要自动落盘,commit()在所有明细插入成功后统一提交,rollback()在出现异常时把所有已执行的 SQL 全部撤销。在关闭自动提交之后,如果用普通的executeUpdate()一条条执行明细插入,性能会很差;这里用addBatch()把多条 SQL 攒在一起,最后executeBatch()一次性发给 MySQL,减少网络往返。Statement.RETURN_GENERATED_KEYS配合getGeneratedKeys()拿到自增主键,是插入主表后获取新订单 ID 的标准做法——不要用“先查 MAX(id)+1”这种写法,并发时会撞车。

4. 从 ZIP 到跑通:导入 IDE、改四处配置、部署 Tomcat 的最小步骤

4.1 改好这四处配置:数据库名、账号、密码、连接 URL

把代码导入 IDE 之前,先检查项目里连接数据库的配置集中在哪。常见的两种方式:一是单独放在src/db.properties文件里,二是直接写在DBUtil.java的常量里。无论哪种,核心就是下面这四个配置项:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/order_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=123456

我一般会把连接串拆分来看,避免改错位置。localhost:3306是 MySQL 的地址和端口,如果 MySQL 装在了其他机器或 Docker 容器里,这里要改成对应 IP 和端口;order_system是数据库名,和第 3 章建表脚本里的CREATE DATABASE order_system保持一致;useUnicode=true&characterEncoding=utf8控制客户端和服务器之间的字符编码,漏掉它会导致中文菜名在 JSP 页面上正常显示,但一写入数据库就变成问号;serverTimezone和useSSL则是 MySQL 8.0 环境下的标配参数。

如果项目里只有DBUtil.java而没有 properties 文件,直接把常量改掉即可。还有一类老项目会把 jdbc 配置写到WEB-INF/web.xml里的<env-entry>标签中,搜索时重点找jdbc:mysql这个字符串,改起来更直观。这里有一个小经验:改完配置后,先单独写一个带main方法的测试类,调DBUtil.getConnection()看能不能拿到连接,跑通再部署到 Tomcat,能把连接问题和 Web 容器问题隔离开来。

4.2 部署到 Tomcat 并验证:启动日志和第一个请求该怎么看

部署这步,不同 IDE 操作路径不一样,但核心都一样:把项目打成 Web 应用,让 Tomcat 指向WebRoot目录。Eclipse 的做法是在 Servers 视图里右键 Tomcat -> Add and Remove -> 把项目添加进去。IDEA 则是 Run Configuration 里选 Tomcat Server Local,Deployment 标签页添加 Artifact。

部署完成后,按顺序做三个验证。第一步看 Tomcat 启动日志,确认没有 Exception;第二步在浏览器访问http://localhost:8080/订单系统名/,如果看到登录页或菜品列表页,说明应用已经起来;第三步打开浏览器 F12 的 Network 面板,刷新页面看 HTTP 状态码,如果登录接口返回 500,说明后端代码或数据库连接有问题,需要回控制台看详细堆栈。也可以用命令行快速验证:

curl -I http://localhost:8080/order_system/login.jsp

curl -I只取响应头,重点看返回的是 200 还是 404。如果返回 404,第一步检查访问的上下文路径和部署时填写的 Application Context 是否一致,常见写法是/order_system;第二步检查web.xml里是不是配置了<welcome-file-list>,直接把index.jsp设为欢迎页,访问根路径可以被自动解析。还要注意 Tomcat 默认端口是 8080,如果本机被占用,可能已经被人改成 8081,启动日志里会有明确提示。

4.3 重新编译后的输出目录:最容易出现“改了没生效”的地方

用 IDE 部署 Tomcat 时,项目源码会被编译到 IDE 指定的输出目录。Eclipse 默认是WebRoot/WEB-INF/classes,IDEA 则是 target 下的结构。很多人在源码里改了DBUtil.java,刷新页面还是老配置,就是因为 IDE 增量编译没有把最新的 class 文件同步到 Tomcat 的工作目录。

我在本地调试时,遇到“改了代码但行为没变”,优先做三件事。第一,Build 菜单里执行 Rebuild Project,强制全量编译而不是增量编译。第二,查看 Tomcat 部署路径下WEB-INF/classes里的.class文件修改时间,如果时间不是刚刚,说明编译产物没过来。第三,如果项目里有out或target目录缓存,直接删掉重新跑。还有一个常见坑是清理 Tomcat 的 work 目录——Tomcat 会把 JSP 翻译成 Servlet 源码再编译成 class,如果 JSP 文件被修改但 work 目录里的缓存没清理,访问到的还是旧页面。平时不太需要手动清,但遇到 JSP 改动后行为怪异,Tomcat/work目录删掉重启一定能解决。

5. 常见问题与避坑:能编译却跑不起来的 5 个典型现场

5.1 驱动类 ClassNotFound:不全怪缺 jar,更多的是驱动版本和新旧命名

现象:Tomcat 一启动,访问首页就报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver,或者com.mysql.cj.jdbc.Driver。

原因分两种。第一种是WEB-INF/lib下根本没有 mysql 驱动 jar 包,这种情况在解压源码包后就缺文件,去 MySQL 官网下载对应版本的 Connector/J 放进lib目录即可。第二种更隐蔽:jar 包在,但驱动类名写错了。老驱动对应com.mysql.jdbc.Driver,MySQL 8.0 的驱动改名为com.mysql.cj.jdbc.Driver。如果你用的是 mysql-connector-java 8.0.x,代码里却还写老名字,Class.forName 同样报找不到。

解决:确认 lib 下 jar 的版本。5.1.x版本用com.mysql.jdbc.Driver,8.0.x版本用com.mysql.cj.jdbc.Driver。如果项目里两种类名都有引用,统一改成和 jar 匹配的那个。有一个快速验证办法,直接解压 jar 包,看META-INF/services/java.sql.Driver文件里写的全限定类名,那就是当前驱动实际支持的入口。

5.2 中文全部变成问号:JSP 页面、请求编码、连接串,三处都得统一

现象:菜品名是中文,页面显示正常,但数据写入数据库后变成???;或者 JSP 页面上直接显示乱码。

原因:字符集在三个环节之间没有对齐。第一,JSP 页面顶部缺少<%@ page contentType="text/html; charset=UTF-8" %>;第二,Servlet 接收请求时没有设置request.setCharacterEncoding("UTF-8"),表单提交的中文参数以 ISO-8859-1 解码;第三,JDBC 连接串缺少characterEncoding=utf8。这三个环节有一个不一致,中文就可能在某一环变成问号。

解决:从上到下统一。JSP 页面顶部加 page 指令,Servlet 的doPost方法第一行写request.setCharacterEncoding("UTF-8"),JDBC 连接串里补useUnicode=true&characterEncoding=utf8。数据库表结构也尽量用utf8mb4,并确认连接时 MySQL 的character_set_server是 utf8mb4。改完以后,把数据库里已经变成???的旧数据删掉重新插入,已经损坏的字符不会自动恢复。

5.3 老源码碰到 Tomcat 10:javax.servlet 变成 jakarta.servlet 之后的翻车

现象:源码包在别人的 JDK 8 + Tomcat 8 上跑得好好的,自己用的是 Tomcat 10/11,一启动就报ClassNotFoundException: javax.servlet.http.HttpServlet,或者 IDE 里大量编译错误。

原因:Tomcat 10 开始被移交到 Jakarta EE 体系,Servlet API 的包名全部从javax.servlet改为jakarta.servlet。老项目源码里所有import javax.servlet.*都找不到对应的 jar,因为 Tomcat 10 自带的 Servlet API 只有jakarta.servlet命名空间。

解决:最省事的做法是把 Tomcat 换成 8.5 或 9.x,不要改代码。如果必须用 Tomcat 10,就需要全局替换 Java 文件和 JSP 里的 import 和 page 指令,比如import="javax.servlet.http.HttpSession"改成import="jakarta.servlet.http.HttpSession"。但要注意,项目 lib 下的 servlet-api.jar 如果还是老的,也要一并换成新版本。对于一套练手项目,换回 Tomcat 9 的成本远低于改所有源码,我个人强烈建议直接用 Tomcat 9。

5.4 MySQL 连接串里的时区与 SSL:8.0 换了驱动就连不上的真原因

现象:JDBC 连接报错,提示The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,或者Communications link failure。

原因:MySQL 8.0 的连接驱动强制要求客户端指定时区,而 MySQL 服务端默认时区在某些系统上返回的是系统本地描述,驱动解析不了。另外 MySQL 8.0 默认启用 SSL 握手,本地开发环境没有配置证书时,连接过程会多一层协商,某些驱动版本会直接失败。

解决:在 JDBC URL 末尾拼上serverTimezone=Asia/Shanghai&useSSL=false。如果用了是 mysql-connector-java 5.1.x 的老驱动,建议直接升级到 8.0.x,老驱动和 MySQL 8.0 服务端的兼容性并不好。还有一类情况是改完参数仍然Communications link failure,这时先去telnet 127.0.0.1 3306确认 MySQL 端口可通,再检查 MySQL 的bind-address配置是否只允许本机连接。

5.5 提交表单就 404:web.xml 的映射和页面 action 必须对得上

现象:点击“登录”或“提交订单”,浏览器地址栏变成对应 Servlet 的映射地址,但页面返回 404,Tomcat 控制台没有任何输出。

原因:JSP 表单的action写的路径和web.xml里<servlet-mapping>的<url-pattern>不一致。比如表单写成action="LoginServlet",而 web.xml 里映射的是/login,请求到达 Tomcat 后找不到对应 Servlet,只能 404。

解决:优先用“相对项目根路径”的方式,在 JSP 里统一写成${pageContext.request.contextPath}/login,这样不管部署时的上下文路径叫什么,都能保证请求发到正确的 Servlet 上。另一个排查点是 Servlet 类上如果用了@WebServlet注解,同时又保留了 web.xml 里的 XML 配置,IDEA 会优先按注解处理,两种映射并存时行为非常迷惑人。这种项目我从头到尾只用一种方式管理映射,要么全部注解,要么全部 web.xml,不要混用。

6. 进阶一步:用 Druid 替换裸 JDBC,再查订单数据是否真落库

项目跑通以后,如果想把这段经历写进简历,或者作为 Java Web 学习成果展示,有一个很小但很加分的改造:把DBUtil里的DriverManager.getConnection换成 Druid 连接池。裸 JDBC 每次请求都要经历“建立连接 - 执行 - 断开连接”的完整过程,而连接池维护一批复用连接,下单场景下的响应时间和数据库压力都有明显改善。

package com.example.util; import com.alibaba.druid.pool.DruidDataSource; import java.sql.Connection; import java.sql.SQLException; public class DBUtil { private static DruidDataSource dataSource; static { dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/order_system" + "?useUnicode=true&characterEncoding=utf8" + "&serverTimezone=Asia/Shanghai&useSSL=false"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setInitialSize(3); // 启动时创建 3 个连接 dataSource.setMaxActive(10); // 最大连接数 dataSource.setMaxWait(3000); // 获取连接超时时间,单位毫秒 } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }

替换的连接池后,业务代码几乎不用动,因为getConnection返回的还是java.sql.Connection接口,DAO 层原样使用。initialSize=3可以让 Tomcat 启动时就预建连接,而不是等第一个用户请求来才连接数据库,避免高峰期首单请求卡顿;maxWait=3000是一个自我保护,当连接被占满时新请求等待 3 秒就放弃,不让客户端一直挂起。

代码改完后,真正要给自己的改造一个交代:验证订单数据是否完整落库。打开 MySQL 命令行,执行下面这条 SQL,把订单主表、用户表、订单明细表关联起来查一次:

SELECT o.id AS order_id, u.username AS 下单用户, o.total_price AS 总金额, o.status AS 状态, COUNT(oi.id) AS 菜品项数, SUM(oi.count) AS 菜品总份数 FROM orders o JOIN user u ON o.user_id = u.id LEFT JOIN order_item oi ON oi.order_id = o.id WHERE o.id = 1 GROUP BY o.id;

如果菜品项数和你在页面上加购的数量一致,说明下单事务没有丢明细。我个人习惯每接手一个老项目,都先做一次这样的数据核对,再去读界面逻辑。把 Druid 换上去这步,顺便帮我养成了看连接池参数的习惯,后来项目里凡是涉及数据库的地方,我都会先确认连接串字符集和时区是否配齐——这两个参数就是老项目里最常见的隐形坑。希望这篇笔记能帮你少走几步弯路,让这个简单网上订餐系统真正跑起来。

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

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

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

立即咨询