ARTICLE DETAIL

资讯详情

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

Java+JSP+MySQL网上订餐系统实战部署指南

Java+JSP+MySQL网上订餐系统实战部署指南 简介这是一套面向Java Web初学者与课程设计者的简易网上订餐系统源码基于JavaJSPMySQL技术栈实现覆盖Servlet处理、MVC分层、数据库交互等核心Web开发实践。资源共1262个文件含48个JSP页面负责用户界面与交互、44个Java类如OrderController、UserDaoImpl、JDBCUtil等体现业务逻辑与数据访问、44个编译后class文件、42个jar依赖包以及大量静态资源491个CSS、415个PNG等支撑前端展示整体压缩包47.38MB结构完整含SQL建库脚本、Eclipse/IDEA项目配置文件及源码使用说明文档。已有385人学习下载开箱即可部署运行适合用于Java Web入门实训、课程设计参考或MVC模式理解实践尤其便于对照源码梳理请求流转路径、数据库表关联及前后端协作机制。1. 这不是“毕业设计摆设”一个能真跑起来的 JavaJSPMySQL 网上订餐系统到底长什么样你搜“JavaJSPMySQL 网上订餐系统源码”十有八九点开的是压缩包名带“毕设”“课程设计”“简易版”的 ZIP 文件——解压后发现首页能进登录页能跳但一点击“下单”就 500 错误数据库建表脚本里user表缺address字段order_item表没加外键约束JSP 页面里混着% out.print(request.getParameter(id)); %这种裸输出连基本 XSS 过滤都没有。这不是代码问题是交付逻辑断层它没按“可部署、可调试、可验证”的工程最小闭环来组织。我手上跑通过的这个版本即标题所指.zip核心价值不在“功能多全”而在每个模块都留了可观察入口、每处报错都指向具体文件行号、每次数据变更都能在 MySQL 命令行里立刻查到对应记录。它适合三类人刚学完 Servlet 生命周期想串起全流程的 Java 新手需要快速搭个内部测试环境验证业务逻辑的中小团队后端或是面试前想亲手 debug 一个真实 Web 层交互链路的求职者。别被“简单”二字骗了——它的价值恰恰藏在那些被教科书省略的胶水代码里比如web.xml里welcome-file-list怎么和index.jsp的重定向形成首屏加载闭环比如DBUtil.java里Connection关闭顺序为什么必须是rs → stmt → conn而不是反过来。接下来我们就从解压那一刻开始把它真正跑起来。2. 解压即启动用最原始的方式把这套 JSP 系统喂活这套系统不依赖 Maven 或 Gradle所有依赖都打在WEB-INF/lib/下这是 JSP 时代最朴素的部署哲学让 Tomcat 自己认出 classpath而不是让构建工具替你猜。我们跳过任何 IDE 导入步骤直接用命令行和文本编辑器建立掌控感。2.1 拆包与目录结构校验先看清它长什么骨架解压后你会看到典型 Java Web 项目结构online-order-system/ ├── WebContent/ ← JSP、HTML、CSS、JS 存放地Tomcat 静态资源根目录 │ ├── index.jsp │ ├── login.jsp │ ├── order.jsp │ └── WEB-INF/ │ ├── web.xml ← 核心配置Servlet 映射、欢迎页、过滤器 │ ├── lib/ ← 所有 JAR 包mysql-connector-java-5.1.47.jar、jstl-1.2.jar 等 │ └── classes/ ← 编译后的 .class 文件含 DAO、Servlet、工具类 └── src/ ← 原始 Java 源码供你修改后重新编译提示WebContent是 Eclipse/MyEclipse 时代的标准 Web Root 名称不是src/main/webapp。若你用 IntelliJ需在 Project Structure → Modules → Web 中手动指定WebContent为 Web Resource Directory否则 Tomcat 启动时找不到 JSP。关键校验点用ls或资源管理器确认WEB-INF/lib/下必须有mysql-connector-java-*.jar版本 5.1.x 最稳8.0 需改驱动类名WEB-INF/classes/下应存在com/order/dao/OrderDAO.class、com/order/servlet/LoginServlet.class等编译产物src/目录存在且包含com.order.dao、com.order.servlet等包结构——这是你后续修改的源头。2.2 数据库初始化用最直白的 SQL 脚本建库建表系统自带db_init.sql通常在根目录或doc/下内容类似-- 创建数据库 CREATE DATABASE IF NOT EXISTS online_order DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE online_order; -- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, -- 注意明文存密码仅用于教学生产必须 BCrypt 加密 phone VARCHAR(20), address TEXT ); -- 菜品表 CREATE TABLE dish ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, description TEXT ); -- 订单主表 CREATE TABLE order ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status ENUM(pending,confirmed,delivered) DEFAULT pending, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user(id) ); -- 订单明细表 CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, dish_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1, price DECIMAL(10,2) NOT NULL, -- 快照价格避免菜品调价影响历史订单 FOREIGN KEY (order_id) REFERENCES order(id), FOREIGN KEY (dish_id) REFERENCES dish(id) );执行步骤以 MySQL 8.0 为例# 1. 登录 MySQL假设 root 密码为空 mysql -u root -p # 2. 执行建库建表注意路径替换为你本地的 sql 文件绝对路径 source /path/to/your/online-order-system/db_init.sql; # 3. 验证表是否创建成功 USE online_order; SHOW TABLES; SELECT COUNT(*) FROM user; -- 应返回 0初始无用户参数说明utf8mb4是必须的否则中文菜名如“宫保鸡丁”可能乱码ENUM类型用于status字段比VARCHAR更节省空间且语义明确order表名用反引号包裹因order是 MySQL 保留字。2.3 Tomcat 配置只改两处让它认出你的应用将整个online-order-system/文件夹不是其子目录复制到 Tomcat 的webapps/目录下重命名为order去掉空格和特殊字符。然后检查两个关键配置conf/context.xml确保Context标签内有reloadabletrue开发阶段允许热加载Context reloadabletrue !-- 其他默认配置 -- /Contextconf/server.xml确认Connector端口未被占用默认 8080Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /启动 Tomcat# Linux/Mac $CATALINA_HOME/bin/startup.sh # Windows %CATALINA_HOME%\bin\startup.bat访问http://localhost:8080/order/—— 如果看到首页通常是index.jsp说明 Web 容器已加载成功。此时打开浏览器开发者工具 → Network 标签刷新页面观察index.jsp的响应状态码是否为 200Response Headers 中是否有Content-Type: text/html;charsetUTF-8。这是第一个可验证的健康信号。3. 从登录到下单跟踪一次完整请求链路看 JavaJSP 如何协作这套系统的价值在于你能清晰看到 HTTP 请求如何穿透 JSP → Servlet → DAO → MySQL。我们以“用户登录”为例走一遍真实调用栈。3.1 JSP 页面不只是模板更是请求发起者打开WebContent/login.jsp关键代码段!-- login.jsp 第 25 行左右 -- form actionLoginServlet methodpost input typetext nameusername placeholder用户名 required input typepassword namepassword placeholder密码 required button typesubmit登录/button /form注意actionLoginServlet—— 这不是指向某个.jsp文件而是直接提交给LoginServlet这个类。这意味着web.xml中必须有对应的servlet-mapping!-- WEB-INF/web.xml -- servlet servlet-nameLoginServlet/servlet-name servlet-classcom.order.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/LoginServlet/url-pattern /servlet-mapping逻辑说明JSP 在这里只承担“渲染表单 发起 POST 请求”的职责不处理任何业务逻辑。所有验证、查询、跳转都交给 Servlet。这是 MVC 中 View 和 Controller 的明确分界。3.2 Servlet 处理接收参数、调用 DAO、控制跳转src/com/order/servlet/LoginServlet.java核心逻辑public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取表单参数注意request.setCharacterEncoding(UTF-8) 必须在 getParameter 前 request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); // 2. 调用 DAO 查询用户此处简化实际应校验密码 UserDAO userDAO new UserDAO(); User user userDAO.findByUsername(username); // 3. 业务判断与跳转 if (user ! null password.equals(user.getPassword())) { // 登录成功存入 session重定向到首页 request.getSession().setAttribute(user, user); response.sendRedirect(index.jsp); } else { // 登录失败设置错误信息转发回 login.jsp保持 URL 不变 request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); } } }参数说明request.setCharacterEncoding(UTF-8)是中文参数不乱码的关键必须在getParameter()之前调用response.sendRedirect()是客户端重定向URL 变request.getRequestDispatcher().forward()是服务器端转发URL 不变二者语义完全不同session.setAttribute()将用户对象存入会话后续页面可通过session.getAttribute(user)获取。3.3 DAO 层用 JDBC 原生操作 MySQL理解连接池为何重要src/com/order/dao/UserDAO.java中findByUsername方法public User findByUsername(String username) { String sql SELECT id, username, password, phone, address FROM user WHERE username ?; User user null; Connection conn null; PreparedStatement pstmt null; ResultSet rs null; try { conn DBUtil.getConnection(); // 从连接池获取连接 pstmt conn.prepareStatement(sql); pstmt.setString(1, username); // 防止 SQL 注入 rs pstmt.executeQuery(); if (rs.next()) { user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setPassword(rs.getString(password)); user.setPhone(rs.getString(phone)); user.setAddress(rs.getString(address)); } } catch (SQLException e) { e.printStackTrace(); // 开发期打印生产应记日志 } finally { // 必须按 rs → pstmt → conn 顺序关闭否则连接泄漏 DBUtil.close(rs, pstmt, conn); } return user; }逻辑说明PreparedStatement预编译 SQL天然防注入DBUtil.close()是封装好的关闭方法其内部严格遵循if (rs ! null) rs.close()→if (pstmt ! null) pstmt.close()→if (conn ! null) conn.close()顺序。若颠倒如先关 connrs和pstmt就无法关闭导致连接池耗尽。4. 避坑指南这 4 个错误让 90% 的人卡在启动第一分钟这套系统看似简单但新手常因环境细节翻车。以下是我在带实习生时统计出的最高频、最隐蔽的 4 个坑每个都附带现象、根因和一招解决。4.1 现象访问http://localhost:8080/order/报 404但http://localhost:8080/能看到 Tomcat 欢迎页原因online-order-system文件夹未直接放在webapps/下而是放在了webapps/online-order-system/子目录中导致 Tomcat 将其识别为context path为/online-order-system而非/order。解决将文件夹重命名为order并确保路径为TOMCAT_HOME/webapps/order/不是webapps/online-order-system/。验证方式启动后查看TOMCAT_HOME/logs/catalina.out搜索Deploying web application directory确认日志中出现Deploying web application directory [/path/to/tomcat/webapps/order]。4.2 现象登录时输入中文用户名后台查不到数据库里存的是乱码如李四原因MySQL 服务端、数据库、表、连接 URL 四层编码未统一为utf8mb4。常见漏点是 JDBC URL 缺少?useUnicodetruecharacterEncodingutf8mb4参数。解决修改src/com/order/util/DBUtil.java中的连接字符串// 错误写法缺参数 private static final String URL jdbc:mysql://localhost:3306/online_order; // 正确写法补全编码参数 private static final String URL jdbc:mysql://localhost:3306/online_order?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai;同时确认 MySQL 配置文件my.cnf中[mysqld]段落有[mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci4.3 现象点击“下单”按钮后页面空白Tomcat 日志报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver原因MySQL 8.0 驱动类名从com.mysql.jdbc.Driver改为com.mysql.cj.jdbc.Driver而源码中仍用旧类名。解决打开src/com/order/util/DBUtil.java找到Class.forName(com.mysql.jdbc.Driver);这行改为// MySQL 5.1.x 用此行 // Class.forName(com.mysql.jdbc.Driver); // MySQL 8.0 必须用此行 Class.forName(com.mysql.cj.jdbc.Driver);并确认WEB-INF/lib/下的mysql-connector-javaJAR 版本 ≥ 8.0如mysql-connector-java-8.0.33.jar。4.4 现象登录成功后跳转到index.jsp但页面顶部显示null用户信息未渲染原因index.jsp中读取 session 的代码写成了% session.getAttribute(user).getUsername() %但未判空。当 session 中user为 null 时JSP 编译后抛NullPointerException页面中断渲染。解决在index.jsp顶部添加空值校验% User user (User) session.getAttribute(user); if (user null) { response.sendRedirect(login.jsp); return; // 立即终止后续 JSP 执行 } % !-- 后续安全使用 user.getUsername() -- 欢迎% user.getUsername() %5. 让它真正可用三个必须动手改的“生产级补丁”这套源码的教学价值在于“可调试”但离“可交付”还差三步。我不会教你加 Spring Boot 或 Vue而是用最原始的 JavaJSP 方式打上三个低成本、高回报的补丁。改完后它就能在测试环境稳定跑一周以上。5.1 补丁一给所有表加created_at和updated_at时间戳字段当前user、dish、order表都没有时间记录导致无法追溯数据何时创建/修改。手动为每张表添加-- 对 user 表 ALTER TABLE user ADD COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP, ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP; -- 对 dish 表 ALTER TABLE dish ADD COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP, ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP; -- 对 order 表注意order 是保留字需加反引号 ALTER TABLE order ADD COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP, ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;为什么重要ON UPDATE CURRENT_TIMESTAMP是 MySQL 的自动更新机制无需在 Java 代码中手动 set 时间。后续所有 INSERT/UPDATE 操作数据库会自动维护updated_at。这是审计追踪的基石也是排查“谁在什么时候改了数据”的唯一依据。5.2 补丁二在 DAO 层统一加入日志输出让每次数据库操作可见在src/com/order/dao/BaseDAO.java或每个 DAO 的基类中添加日志方法import java.util.logging.Logger; public abstract class BaseDAO { protected static final Logger logger Logger.getLogger(BaseDAO.class.getName()); protected void logSQL(String sql, Object... params) { StringBuilder sb new StringBuilder(Executing SQL: ).append(sql); if (params.length 0) { sb.append( with params: ); for (int i 0; i params.length; i) { sb.append(params[i]); if (i params.length - 1) sb.append(, ); } } logger.info(sb.toString()); } }然后在UserDAO.findByUsername()开头加入logSQL(SELECT id, username, password, phone, address FROM user WHERE username ?, username);效果启动 Tomcat 后logs/catalina.out里会出现类似INFO: Executing SQL: SELECT ... with params: 张三的日志。当你发现某个页面加载慢直接 grep 日志就能定位是哪条 SQL 慢而不是靠猜。5.3 补丁三用Filter统一处理中文乱码告别每个 Servlet 写setCharacterEncoding创建src/com/order/filter/EncodingFilter.javapublic class EncodingFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; // 统一设置请求和响应编码 req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); } }并在web.xml中注册filter filter-nameEncodingFilter/filter-name filter-classcom.order.filter.EncodingFilter/filter-class /filter filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern !-- 拦截所有请求 -- /filter-mapping玄学经验这个 Filter 必须放在web.xml中所有其他 Filter如登录拦截之前否则后续 Filter 获取的request.getParameter()仍是乱码。它是整个 Web 层的“编码守门员”一劳永逸。6. 验证与压测用 curl 和 MySQL 命令行做最小闭环测试最后一步别急着点鼠标。用两条命令证明你的系统真的“活”了且数据流是通的。这是工程师的“后悔药”——当 UI 出问题时你能绕过前端直击后端和数据库。6.1 用 curl 模拟登录验证 Servlet 接口# 发送 POST 登录请求假设已有用户 admin 密码 123 curl -X POST http://localhost:8080/order/LoginServlet \ -H Content-Type: application/x-www-form-urlencoded \ -d usernameadmin \ -d password123 \ -i # -i 参数显示响应头确认是否 302 重定向预期响应HTTP/1.1 302 Found Location: index.jsp Set-Cookie: JSESSIONIDABC123...; Path/order; HttpOnly关键点看到302 Found和Location: index.jsp说明 Servlet 逻辑正确Set-Cookie头证明 session 已创建。如果返回200 OK且 body 是 HTML说明response.sendRedirect()未生效可能是web.xml中LoginServlet的 mapping 路径错了。6.2 用 MySQL 命令行验证数据写入登录 MySQL执行USE online_order; -- 查看用户表确认 admin 用户存在 SELECT id, username, created_at FROM user WHERE username admin; -- 查看最近一条订单模拟下单后 SELECT o.id, o.total_price, o.status, u.username FROM order o JOIN user u ON o.user_id u.id ORDER BY o.created_at DESC LIMIT 1;参数说明LIMIT 1配合ORDER BY ... DESC是查“最新一条”的标准写法JOIN语句把订单和用户信息关联一眼看出是谁下的单。如果这里查不到数据说明 DAO 层的insert方法没执行或事务未提交检查OrderDAO.addOrder()中是否有conn.commit()。6.3 一个血泪经验永远在finally块里关连接哪怕只有一行代码我见过太多人把conn.close()写在try块末尾结果rs.next()抛异常后conn就永远不关闭了。正确的姿势只有一种Connection conn null; try { conn DBUtil.getConnection(); // ... 执行查询 } catch (SQLException e) { e.printStackTrace(); } finally { if (conn ! null) { try { conn.close(); // 这里必须判空再关 } catch (SQLException e) { e.printStackTrace(); } } }但这还不够——真正的工业级写法是DBUtil.close(rs, pstmt, conn)因为它把“判空 关闭 捕获异常”封装成一行。你不需要记住顺序只需要相信这个方法。这就是为什么我坚持让你从DBUtil.java开始读源码它不是装饰是系统的呼吸节律。希望帮到你。本文还有配套的精品资源点击获取
返回列表