JavaEE餐厅点菜系统课程设计:JSP/Servlet/JDBC全链路解析
2026/9/16 21:46:10 网站建设 项目流程

简介:一套基于Java Eclipse开发的餐厅点菜系统完整源码与课程设计说明文档,适合Java初学者、软件工程课程设计的学生以及需要快速搭建点餐Demo的开发者。项目围绕点餐业务流程展开,包含需求可行性分析、系统设计、编码与测试等阶段,可帮助读者理解从需求分析到系统实现的全过程。压缩包共47个文件,体积1.4MB。其中9个Java源码文件为主体程序,10个class为编译输出,20个jpg为菜品展示或界面素材,另含project、classpath、xml等Eclipse工程配置文件以及docx说明文档,结构清晰,可导入Eclipse直接查看运行。已有1784人学习使用。资源附带的说明文档涵盖了课程设计目的、任务、开发环境、系统可行性分析及需求模型等内容,源码与文档互为对照,便于撰写实验报告或答辩展示;目录中按工程结构组织,适合作为快速上手的参考案例。

1. 点菜系统不是摆设:这份 JavaEE 课程设计里值得翻的东西

很多人看到“点菜系统”四个字,会习惯性以为是从菜名 CRUD 到“加购物车”的玩具项目。这套基于 Java Eclipse 的餐厅点菜系统源码,实际把 JSP/Servlet/JDBC 的完整链路都走了一遍:前端用 JSP 展示菜品分类和图片,后端维护订单与明细,说明文档还包含需求分析过程,工程文件.project.classpathbuild目录也保留完整。对刚完成 java 基础学习、第一次接触 Web 容器的人来说,它是理解动态网页项目如何打包部署的最佳样本;对写过 Spring Boot 的开发者,也能借它对比传统 Servlet 的路由与HttpSession管理差异。下面不按教程腔铺开,直接从工程文件讲起,因为课程设计翻车最多的位置,往往不在业务代码本身,而在工程配置和类加载边界。

2. 先解剖 Eclipse 工程结构再谈代码:.project、.classpath 与实体设计

2.1 从 .classpath 和 build 目录反推依赖

拿到压缩包后不要急着解压拖进 Eclipse。先用文本编辑器打开.classpath,可以看到项目的 JRE 容器、源码目录和输出目录。典型内容是这样:

<?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="src"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="output" path="build/classes"/> </classpath>

这个文件说明两点:源码目录是src,编译输出目录是build/classes,而不是默认的 bin。许多同学改了代码刷新页面仍看到旧版本,往往就是没弄懂 Eclipse 自动构建输出到了哪里。kind="con"那一行表示依赖容器是 JRE,而不是 Maven 仓库里的 jar,说明项目没走 maven 管理依赖,后续加入 MySQL 驱动只能手动拷贝到WEB-INF/lib

如果压缩包内还包含.settings/org.eclipse.jdt.core.prefsorg.eclipse.wst.common.component,说明它是一个被 WTP(Web Tools Platform)管理的 Dynamic Web Project。导入时应该用 File > Import > Existing Projects into Workspace 而不是 New Project,否则.classpath里的相对路径会失效,Eclipse 会完全不理srcbuild的关系。我一般会打开.settings/org.eclipse.jdt.core.prefs,确认编译器 compliance level 与当前 JDK 匹配;课设环境多为 JDK 8 或 11,如果本机换了高版本 JDK,JSP 预编译阶段可能报选项冲突。

提示:.project.classpath这类文件属于 Eclipse 工程元数据,提交 Git 时不要删,依赖它们才能让队友直接 Import 后跑起来。

2.2 实体类到数据库表的映射关系

这套系统的源码里,核心类围绕几个业务对象:菜品(Dish)、分类(Category)、订单(Order)、订单明细(OrderItem)。对照说明文档里的需求分析,可以整理出一张映射关系:

实体类(Java Bean)对应数据表关键字段用途
Dish 菜品实体t_dishdish_id, dish_name, price, type_id, img_name菜单列表与点餐展示
Category 分类实体t_categorycat_id, cat_name菜品分类筛选
Order 订单实体t_orderorder_id, table_no, total_price, create_time订单主表
OrderItem 明细实体t_order_detaildetail_id, order_id, dish_id, quantity每道菜份数与小计

实体类的写法是课设里最朴素的一种,直接对应数据库字段:

public class Dish { private Integer dishId; // 菜品ID,与 t_dish.dish_id 对应 private String dishName; // 菜品名称 private Double price; // 单价 private Integer typeId; // 分类ID private String imgName; // 图片文件名,如 jichi.jpg public Integer getDishId() { return dishId; } public void setDishId(Integer dishId) { this.dishId = dishId; } // 其余 getter / setter 省略 }

这里我刻意把getter/setter只写一个,是为了提醒你:如果类属性与表字段命名不一致,查询结果通过rs.getXxx("字段名")手工装配时容易写错。常见做法有两种,一种是实体属性完全对齐表字段,省去映射代码;另一种是使用BeanUtils.populate反射复制,但那样需要强制转换成字符串数组,对初学者来说不如手写稳妥。项目里连续的jichi.jpgkele.jpgshutiao.jpgdanta.jpg这些图片名,直接存到img_name字段里,展示时统一拼images/前缀,不要每页写死。

2.3 说明文档与代码不一致时以哪边为准

压缩包里有一份基于Java Eclipse 设计餐厅点菜系统设计.docx,内容覆盖系统可行性分析和需求建模,但并不意味着代码里就能找到对应模块。课设包的常态是:文档描述“先提交订单后付款”,代码里没有支付模块;文档写了用户管理,代码可能只有管理员登录一个user.jsp。这不是残缺,而是课程设计阶段只要求做核心闭环。

对照时我一般以源码为准。先找出 Servlet 的web.xml或注解里的路由映射,确认实际存在哪些页面,再看 JSP 里action指向哪个 Servlet,最后反推出表结构。如果说明文档里连表结构都没给,就用实体类字段倒推建表语句,这种从成品反推需求的能力,答辩时老师反而会认可。

3. 点餐主流程的代码实现:JDBC 查询、HttpSession 购物车与订单落库

3.1 封装 JDBC 工具类,先解决连接与释放

点菜系统业务再简单,也绕不开数据库连接。课程设计里频繁开关Connection,如果每处都写try-catch-finally,很容易出现资源泄漏。这种体量不需要上连接池,写一个轻量工具类就够了:

public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/restaurant?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }

这段代码里三个参数分别说明一下。useUnicode=true&characterEncoding=utf-8决定写入数据库的中文会不会变成???,缺了它课程设计在中文菜名上必然翻车。serverTimezone=Asia/Shanghai是 MySQL 8.x 连接串的常见要求,老教材不写也能跑,但高版本缺省 UTC 会导致时间字段差 8 小时。Class.forName在 JDBC 4.0 以后可以省略,但保留下来能体现对驱动注册机制的理解,面试时还能顺带讲一句 SPI 自动加载,这是加分项。密码硬编码没问题,交作业前记得把USERPASSWORD改成常量,最后再说明文档里标注即可。

3.2 菜单加载与购物车的职责边界

菜单加载走典型的 DAO 模式。DishDAO里写findByCategory(int typeId)findAll(),Servlet 拿到List<Dish>后 setAttribute 传给 JSP。这部分没有难点,重点在购物车放哪里。购物车只在一个点餐会话内有效,不需要持久化,直接存HttpSession是最合理的选型;如果放进数据库,用户还没结账就产生大量临时记录,还得定期清理。

购物车结构用Map<Integer, CartItem>而不是ArrayList,key 直接压菜品 ID,方便合并相同菜品:

public void addToCart(HttpSession session, Dish dish, int quantity) { Map<Integer, CartItem> cart = (Map<Integer, CartItem>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<Integer, CartItem>(); session.setAttribute("cart", cart); } CartItem item = cart.get(dish.getDishId()); if (item == null) { cart.put(dish.getDishId(), new CartItem(dish, quantity)); } else { item.setQuantity(item.getQuantity() + quantity); } }

逻辑说明:先从 session 取购物车,若为空则初始化一个 HashMap 放回去;再检查菜品 ID 是否已存在,存在就把份数累加,否则新增条目。这样点击十次“加一份”只会产生一条明细,而不是十条。

参数说明里有个容易被忽略的边界:quantity必须做上限校验。HttpSession默认保存在服务端内存,如果单个用户往购物车塞几万条数据,内存会被拖垮。我一般会加一个同样的判断:单个菜品数量上限 99,购物车总条目上限 50,超出直接 return 并提示“超出下单数量限制”。这个点也是很多 java 面试题里围绕 session 存储开销展开的追问方向,值得提前想清楚回答。

3.3 下单逻辑的事务边界与订单号生成

生成订单要同时写主表和明细表,必须保证处于同一事务。课程设计里最常见的坑,是在两个 DAO 里分别调用 insert,第二条失败后主表只剩一条脏数据。标准做法是在 Service 层拿同一个连接,手动控制事务:

public boolean createOrder(Order order, List<CartItem> items) { Connection conn = null; PreparedStatement psMain = null; PreparedStatement psDetail = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); String sqlMain = "INSERT INTO t_order(table_no, total_price, create_time) VALUES(?,?,NOW())"; psMain = conn.prepareStatement(sqlMain, Statement.RETURN_GENERATED_KEYS); psMain.setString(1, order.getTableNo()); psMain.setBigDecimal(2, order.getTotalPrice()); psMain.executeUpdate(); ResultSet rs = psMain.getGeneratedKeys(); Long orderId = null; if (rs.next()) { orderId = rs.getLong(1); } String sqlDetail = "INSERT INTO t_order_detail(order_id, dish_id, quantity) VALUES(?,?,?)"; psDetail = conn.prepareStatement(sqlDetail); for (CartItem item : items) { psDetail.setLong(1, orderId); psDetail.setInt(2, item.getDish().getDishId()); psDetail.setInt(3, item.getQuantity()); psDetail.addBatch(); } psDetail.executeBatch(); conn.commit(); return true; } catch (SQLException e) { try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } return false; } finally { if (psDetail != null) psDetail.close(); if (psMain != null) psMain.close(); if (conn != null) conn.close(); } }

事务边界说明:setAutoCommit(false)之后,主表和明细表的写入都一样未提交;直到commit()才真正落库。finally里必须按 ResultSet、Statement、Connection 的顺序倒序释放,这个顺序比代码本身更值得解释。

订单号如果直接用数据库自增 ID,打印小票时会暴露经营规模,也容易被人批量遍历。改进方式是用时间加随机数拼接:String orderNo = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + String.format("%03d", (int)(Math.random()*1000))。同秒内最多产生千分之一的冲突可能,表里再给order_no加唯一索引兜底,就足够课设演示了。

4. Eclipse + Tomcat 部署:驱动、连接串与类加载异常定位

4.1 从 Eclipse 启动 Tomcat 的最小配置

把工程挂到 Tomcat 上,不要直接在 Preferences 里瞎点。右键工程 > Properties > Targeted Runtimes,勾选本机已配置的 Apache Tomcat 运行时,确保 WTP 插件能把这个工程识别成 Dynamic Web Module。然后在 Server 视图新建一个 Tomcat 实例,右键该实例选择 Add and Remove,把点菜系统工程加进去。

启动之后,发布目录通常位于工作空间下的.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/你的工程名。如果改了 JSP 后刷新页面没变化,先看这个目录里的 jsp 文件有没有更新,再决定是 clean Tomcat 工作目录还是重新 Publish。源码包里那个connectorModuleMETA-INF目录,就是 WTP 在发布时生成的模块描述信息,不用手工改动。

4.2 MySQL 驱动放进 WEB-INF/lib 而不是只配 Build Path

很多初学者在 Build Path 里加了mysql-connector-java.jar,编译通过,Tomcat 启动后一执行查询就报ClassNotFoundException: com.mysql.jdbc.Driver。原因是:Build Path 只作用于编译期,运行期的类加载由 Tomcat 的 WebappClassLoader 负责,它只扫描WEB-INF/classesWEB-INF/lib下的类。这是 Web 容器类加载隔离的典型机制,也是上面 2.1 节没走 Maven 时才需要手动做两遍的原因。

catalina.bat run在控制台运行时,也可以通过这样的命令做粗略验证:

echo %JAVA_HOME% java -version dir "%TOMCAT_HOME%\lib\mysql*.jar"

第一行确认环境变量,第二行确认 JDK 版本,第三行看驱动 jar 是否真的存在于 Tomcat 的 lib 目录。如果驱动放在全局 lib 而非工程内,换机器部署就会失效,所以课程设计还是统一放进WEB-INF/lib最稳。连接串参数参考 3.1 节,MySQL 5.7 与 8.x 对serverTimezone的处理不一致,缺这一参数高版本会直接抛 SQLException。

4.3 三个课堂项目里高频出现的部署报错

报错现象常见原因处理思路
找不到或无法加载主类 org.apache.catalina.startup.bootstrapJAVA_HOME 指向了 JRE 或路径包含空格重新配置 java 环境变量,确保%JAVA_HOME%\bin\java.exe存在
访问工程名返回 404Context Root 与发布名不一致右键工程 > Properties > Web Project Settings 修改 Context Root
ClassNotFoundException: com.mysql.jdbc.Driverjar 没放WEB-INF/lib把驱动拷贝到该目录后 Clean Tomcat 再重启

org.apache.catalina.startup.bootstrap这个主类代表的是 Tomcat 启动入口,JAVA_HOME 配错时,启动脚本找不到java.exe,自然无法加载类。排查这类问题不要只盯 Tomcat 日志,先回到终端执行echo %JAVA_HOME%,确认路径没有指向C:\Program Files\Java\jre...这种 JRE 目录。Tomcat 是 Eclipse 环境下对类加载最敏感的组件,把这几个报错背下来,做任何 Java 课程设计都能派上用场。

# 运行时输出实际的 class path,快速定位驱动来源是否重复 java -jar my-little-probe.jar

注意:如果同时在 Tomcat 的 lib 和 WEB-INF/lib 下放了不同版本的 MySQL 驱动,会出现按下不表诡异的No suitable driver,两个目录只保留一个版本。

5. 给课设加三道保险:图片缓存、参数校验与从点餐到叫号的扩展

5.1 菜品图片的静态资源映射与浏览器缓存

源码里的kfc.jpghanbao.jpg这些图片,放在WebContent/images/下,JSP 里用images/kfc.jpg相对路径直接引用。相对路径有个坑:如果 JSP 页面位于WEB-INF/content/子目录,相对路径要写成../../images/kfc.jpg才有效,建议用<c:url>${pageContext.request.contextPath}拼接。图片文件较少时浏览器每次刷新都重新请求,没有太大影响;如果菜品图片多,可以在过滤器中给静态资源加上缓存头:

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) { HttpServletResponse response = (HttpServletResponse) resp; response.setHeader("Cache-Control", "public, max-age=86400"); chain.doFilter(req, resp); }

max-age=86400表示浏览器在一天内优先使用本地缓存,配合 ETag 校验能在菜谱图片数量膨胀时明显减少无意义请求。注意这个过滤器只应作用于/images/*路径,避免order.jsp等动态页面也被缓存。

5.2 下单接口的金额校验与 SQL 注入防护

一个常见的课设安全问题,是 JSP 里把total_price当成 hidden 隐藏域提交到后端。客户端完全可以改价格再提交,所以后端必须重新计算总价:服务端拿到购物车里的菜品 ID 和数量,去t_dish表重新查询单价,再累加出金额。数据库里的price属于可信数据,页面传上来的price一律忽略。

SQL 注入防护同样统一用PreparedStatement占位符:

String sql = "SELECT * FROM t_dish WHERE dish_name LIKE ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, "%" + dishName + "%");

参数说明:LIKE查询里的百分号不能写进 SQL 模板,要放进参数值,否则setString转义失效,用户输入'_''%'会改变匹配语义。这一点与 MyBatis 源码里#{}预编译的设计逻辑是一致的,把数据库操作的字符串拼接问题想清楚,后续学 ORM 框架会顺畅得多。

5.3 从桌边点餐扩展到取餐叫号的最小改动

课程设计做到这里,想往上加亮点最简单的方向是加一个订单状态字段。在t_order表增加status列,用 0 表示已下单、1 表示制作中、2 表示可取餐,JSP 里用一个下拉框或按钮去触发表单提交,更新语句只需要一段:

String update = "UPDATE t_order SET status = ? WHERE order_id = ?"; PreparedStatement ps = conn.prepareStatement(update); ps.setInt(1, 2); ps.setLong(2, orderId);

这样改动的面很小:DAO 增加一个updateStatus方法,后台管理页面循环出未完成订单时,把按钮的value绑定到状态值,前端页面用onclick发起请求。把整套点餐系统从单纯的“点菜下单”扩展成“下单-制作-取餐”状态机,再配合一张简单的叫号展示页,答辩时就能把业务完整度和代码组织度一起体现出来。

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

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

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

立即咨询