JSP网上订餐系统毕业设计:从Servlet到JDBC的完整Java Web实践

发布时间:2026/10/8 2:54:23
JSP网上订餐系统毕业设计:从Servlet到JDBC的完整Java Web实践 简介这是一套基于JSP的网上订餐管理系统毕业设计资料包面向计算机相关专业学生和初级Web开发者适合作为毕业设计、课程设计或项目实训参考。系统完整实现网上订餐业务包含前台订餐、后台管理等模块集中呈现JSPServlet数据库的典型Web开发链路。压缩包约95.81MB内含项目报告、答辩PPT、源代码、数据库、系统截图和部署视频等主要文件覆盖从设计、编码到部署演示的完整流程。其中项目报告阐述需求分析、系统架构、数据库ER建模与软件测试兼顾软件工程方法与用户界面设计要点答辩PPT便于整理展示思路部署视频直观演示Tomcat环境配置及应用发布步骤。已有919人浏览学习对希望系统了解Web应用全流程的读者是一份结构清晰、实用价值较高的参考资料。1. 基于JSP的网上订餐管理系统一个毕设压缩包背后值得做完讲透的方向每到毕业季很多学生手里都会出现这样一份压缩包基于JSP的网上订餐管理系统带项目报告、答辩PPT、源代码、数据库、截图和部署视频。这套东西看起来技术栈偏老但骨子里是一条完整的Java Web业务线前台点餐、后台管理、购物车、订单状态流转、数据库增删改查。业务闭环完整、代码量一个人能维护、每一层都能被答辩老师问到也能答上来这是它被反复用来做毕业设计的原因。适合计算机相关专业学生当主项目也适合刚学完Servlet/JSP的开发者拿来当第一个完整项目练手。这篇就按复现路径把这个方向拆开讲清楚。2. 技术选型与分层设计为什么不盲目上Spring Boot2.1 先看毕业设计的评分维度再选技术栈毕业设计不是公司预发环境评分看的是几件具体的事业务是否完整、代码结构是否清晰、数据库设计是否合理、文档和答辩是否自圆其说。在这四个维度里JSPServletJDBC这套组合不丢分反而在“代码结构”和“答辩追问”两个环节有天然优势。常见的误区是一上来就套Spring Boot甚至微服务结果项目像拼凑注解背后的原理一问三不知。框架是黑匣子出了问题你连日志都看不懂而Servlet和JDBC是透明的报错直接指向业务代码老师追问到哪一层都能接住。这个判断还有一个现实支撑JSP项目对应的教材、课程实验和网上排错文章极其成熟任何一条报错信息基本都有前人踩过坑。相比之下新框架的报错往往要翻GitHub issue对毕设周期来说成本更高。所以我的建议是学校没有明确要求Spring Boot的情况下网上订餐这种业务规模用JSP最稳如果你已经会Spring Boot也不要急着否定这套方案你要做的不是炫技是把业务讲圆。2.2 包结构先行把前后台分层这件事写进代码拿到源码之后第一件事不是看单个文件而是看包名。一个装配好的JSP网上订餐项目目录划分大概长这样OnlineOrder/ ├── src/main/java │ ├── com.order.entity // 实体类User、Dish、Orders等 │ ├── com.order.dao // 数据访问层负责JDBC和SQL │ ├── com.order.service // 业务层购物车、下单、库存 │ ├── com.order.servlet // 控制层接收请求、跳转页面 │ ├── com.order.filter // 登录鉴权、编码过滤 │ └── com.order.util // DBUtil、字符串工具 ├── src/main/webapp │ ├── user/ // 前台页面登录、菜单、购物车 │ ├── admin/ // 后台页面菜品管理、订单管理 │ ├── images/ // 菜品图片、静态资源 │ ├── WEB-INF/web.xml │ └── index.jsp └── sql/order_db.sql // 建库建表脚本这个划分把前台和后台从物理路径上分开答辩讲PPT时可以指着目录说“前台页面在user下后台管理在admin下数据库脚本独立放在sql目录里”。这里有一条红线JSP页面里不要直接写JDBC代码。如果把数据库连接写在页面里后面调试连接池、乱码、事务都会非常被动。正常的依赖方向是servlet到service再到dao最后统一走DBUtil取连接JSP只负责展示和提交表单。如果拿到手的源码没有按这个结构组织也先别急着重构先把web.xml看懂看Servlet映射了哪些URL再看JSP表单提交到哪个地址数据流就能摸出来。有的项目会省略web.xml改用WebServlet注解这时候整理映射的入口就成了全局搜索WebServlet。两种风格不要混用同一套项目里又用web.xml又用注解很容易出现访问路径冲突部署阶段排查起来特别费劲。2.3 环境搭配清单版本选对了部署少走一半弯路JSP项目最常见的翻车原因不是代码错而是JDK、Tomcat、MySQL的版本互相不搭。我一般建议按这套组合走组件推荐版本说明JDK1.8最稳定兼容绝大部分Tomcat和IDE配置Tomcat8.5 或 9.0支持Servlet 3.0/4.0JSP项目的主力版本MySQL5.7 或 8.05.7用户多8.0要注意驱动和时区参数IDEEclipse/IDEA按非Maven的动态Web项目导入MySQL驱动5.1.x配5.78.0.x配8.0驱动大版本必须匹配数据库大版本如果本机已经装了Tomcat 10老项目大概率跑不起来因为Tomcat 10把javax.servlet整体换成了jakarta.servlet老代码里到处是import javax.servlet直接编译失败。这个问题很隐蔽不少人的项目红叉起源于此而不是代码本身有问题。真遇到这个情况我会再装一个Tomcat 8.5两个Tomcat用不同端口并存不影响本机其他项目。MySQL 8.0还有一个坑是驱动类名从com.mysql.jdbc.Driver换成了com.mysql.cj.jdbc.Driver连接串必须带serverTimezone参数这些在第4章会专门讲。项目导入IDE时也注意选择Dynamic Web Project方式不要选Maven——大量毕设源码没有pom.xml用Maven导入会报缺依赖反而把结构搞乱了。导入后把mysql驱动jar复制到WEB-INF/lib目录漏了这一步所有数据库操作都会报ClassNotFoundException。3. 核心功能实现从登录鉴权到订单闭环的关键路径项目代码看起来多真正决定业务闭环的是两条数据流登录鉴权一条点餐下单一条。这两条通了剩下管理端都是数据库增删改查。下面按数据流顺序把关键代码过一遍。3.1 登录鉴权一个Filter挡在受保护页面外面网上订餐系统的第一道关是登录。用户不登录只能看菜单点了“加入购物车”或“去结算”要跳回登录页管理员不登录进不了后台。用Filter统一做这件事比在每一个Servlet里判断session省事得多public class LoginFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(); String uri req.getRequestURI(); // 放行登录页、登录接口、静态资源 if (uri.endsWith(login.jsp) || uri.endsWith(loginServlet) || uri.contains(/images/) || uri.endsWith(index.jsp)) { chain.doFilter(request, response); return; } Object user session.getAttribute(user); if (user null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } if (uri.contains(/admin/) !admin.equals(((User) user).getRole())) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }逻辑说明这里先放行登录页、登录接口和静态资源再检查session里有没有user没有就重定向到登录页。最后一段是针对后台的二次校验普通用户直接访问/admin/路径也会被弹回。放行列表建议抽成配置维护不要继续堆if否则每加一个公开页面都要改Filter。参数说明Filter映射到/会拦截所有资源图片和CSS全挂是新手经常碰到的现象所以放行判断里images目录必须处理。实际项目中有人把Filter只拦/admin/前端页面让每个Servlet自己判断这样也行但工作量大不推荐。登录Servlet里还要注意密码校验的位置先把查库结果取出来再判断是否为null很多空指针异常都是把rs.next()的结果直接拿来getString导致的。用户表的密码字段要单独定义getter不要把整个对象转成JSON打印到日志不然密码容易在答辩日志里暴露。3.2 菜品列表与购物车Session里存一张HashMap前台核心数据流是“看菜、加购物车、下单”。购物车不需要建表每个用户一个购物车放在自己的Session里即可。常见的实现是在CartServlet里// CartServlet doPost HttpSession session request.getSession(); HashMapInteger, CartItem cart (HashMapInteger, CartItem) session.getAttribute(cart); if (cart null) { cart new HashMap(); } int dishId Integer.parseInt(request.getParameter(dishId)); int count Integer.parseInt(request.getParameter(count)); Dish dish dishDao.findById(dishId); if (dish null) { response.sendRedirect(menu.jsp?msg菜品不存在); return; } CartItem item cart.get(dishId); if (item null) { cart.put(dishId, new CartItem(dish, count)); } else { item.setCount(item.getCount() count); // 同一道菜累加数量 } session.setAttribute(cart, cart); // 放回Session response.sendRedirect(cart.jsp);逻辑说明dishId作为Key同一道菜多次添加只更新数量不需要在数据库里建购物车表Session销毁购物车自动清空正好符合“未登录不能下单”的业务约束。参数说明CartItem里至少要有dish对象、count数量和小计金额三个字段金额计算放在service层不要在JSP页面里做除法浮点误差和格式问题会让你在答辩现场丢分。Integer.parseInt这里没有做异常处理如果前端传了非数字参数会抛异常毕设里可以在表单层用input的类型约束兜底再写一句提示。菜品列表页推荐用JSTL的c:forEach渲染这样JSP页面里能少写很多Java代码。如果项目里没有引入JSTL也可以直接用Scriptlet循环输出表格但页面会很难看答辩时老师翻到源码会皱眉。3.3 提交订单与事务边界一个方法里保证扣库存和录订单同时成功从购物车到订单是整条业务链最关键的一步。用户点“提交订单”后台要同时做三件事往orders表插一条订单主记录往order_detail表插入购物车里的每一项扣减dish表对应菜品的库存。这三件事必须在一个事务里public boolean createOrder(Order order, HashMapInteger, CartItem cart) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务先别自动提交 orderDao.insert(conn, order); // 拿到orderId并回填到order对象 for (CartItem item : cart.values()) { orderDetailDao.insert(conn, order.getId(), item); dishDao.decreaseStock(conn, item.getDish().getId(), item.getCount()); } conn.commit(); // 全部成功一起提交 return true; } catch (Exception e) { if (conn ! null) { DBUtil.rollback(conn); // 任一步失败回滚 } log.error(下单失败, e); return false; } finally { DBUtil.close(conn, null, null); } }逻辑说明dao层的方法都显式接收Connection参数而不是在dao内部各自new连接事务边界才能控制在service层。关键注意点连接不能中途close再get否则事务就断了。decreaseStock里要写“UPDATE dish SET stock stock - ? WHERE id ? AND stock ?”用条件更新防止库存被扣成负数影响行数为0说明库存不足dao返回falseservice层抛出业务异常提示用户。参数说明订单金额total在下单时算好写进orders表作为快照之后菜品涨价也不影响历史订单。事务这里还有一个常见取舍毕设项目用JDBC手动管理事务就够不一定要加连接池和声明式事务。但至少要明白“一组数据库操作要么全成功要么全失败”的判断标准——用户在提交订单后刷新页面如果事务边界乱了可能出现订单表有记录而明细表空白的半截数据。答辩老师非常爱问这个点。3.4 个人信息展示与管理端增删改查把用户中心和管理端补齐用户登录后进入个人中心要能看到昵称、电话、收货地址和历史订单。这种JSP个人信息展示页面的做法是从session拿user对象的id再调userDao.findById刷新一次数据因为电话和地址可能在编辑后变了不能只读session里的旧值// UserInfoServlet int userId ((User) session.getAttribute(user)).getId(); User fresh userDao.findById(userId); request.setAttribute(user, fresh); // 放进request页面EL表达式取值 request.getRequestDispatcher(user/info.jsp).forward(request, response);逻辑说明登录时放进session的user是快照编辑资料后必须重新查一次库再把新对象放回request否则页面上显示的还是旧数据。参数说明这里用forward而不是sendRedirect因为要带user对象到页面如果用了redirectrequest里的属性会丢页面又要回数据库查一遍。管理端的菜品管理是标准的数据库增删改查DishServlet通过action参数区分add、update、delete、list四种操作。actiondelete时注意外键约束菜品一旦被订单明细引用就不能物理删除正确做法是加一个status字段做逻辑下架数据库保留数据前台菜单不再展示。这个细节在项目报告里可以单独写一段说明你考虑了数据完整性答辩时是加分项。4. 数据库设计与初始化表结构直接决定你能不能讲清楚业务数据库设计是整个毕设的地基表结构一乱代码层后面全要返工。网上订餐系统的库不大但关系建模的思路要清晰。4.1 五张核心表从关系上理解业务网上订餐系统的数据库一般围绕五张表展开关系不复杂但每张表的位置都很明确。表名关键字段说明userid, username, password, role, phone, addressrole用user/admin区分前后台categoryid, name, sort菜品分类如川菜、饮品dishid, category_id, name, price, stock, image, status属于一个分类status控制上下架ordersid, user_id, total, status, create_timestatus待配送、已配送、已取消order_detailid, order_id, dish_id, count, price订单明细挂在orders下user和orders是一对多orders和order_detail是一对多dish和order_detail是多对一category和dish是一对多。这个模型覆盖了“用户下单、菜品管理、订单跟踪”三条业务线。做毕设时不需要再额外加冗余表五张表够讲清楚表太多容易在答辩时顾此失彼。orders里保留total字段很有必要它把下单那一刻的金额固定下来菜价后面改了历史订单数据不受影响。order_detail里的price字段同理它是这份订单当时的成交单价不是菜品当前价格。两个金额字段都算快照避免数据库只能靠实时计算来回溯订单金额。4.2 建表SQLutf8mb4、外键和初始管理员数据库脚本第一个坑是字符集。JSP页面和数据库字符集不一致存中文就乱码。建库建表的正确写法CREATE DATABASE IF NOT EXISTS order_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE order_db; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role VARCHAR(10) DEFAULT user, phone VARCHAR(20), address VARCHAR(200) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE dish ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 100, image VARCHAR(200), status TINYINT DEFAULT 1, CONSTRAINT fk_dish_category FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO user (username, password, role) VALUES (admin, admin123, admin);逻辑说明utf8mb4是必选项它比utf8多覆盖四字节字符emoji和生僻字都能存。外键约束在毕设项目里建议保留它能把数据库层的错误提前暴露出来比如category被删但dish还在引用数据库会直接拒绝业务层发现得早。初始管理员账号直接写在SQL脚本里部署后不用手动跑SQL加账号。参数说明password用明文只是为了教学演示答辩老师问起来你可以补一句“生产环境会用MD5加盐或bcrypt做密码哈希”这句话是加分项但毕设里不必真的去做。注意脚本顺序先建category再建dish因为有外键依赖顺序反了会直接报错。导入SQL也有固定套路在MySQL命令行执行mysql -uroot -p order_db order_db.sql导入报错时先看具体失败的是哪一行常见原因是SQL脚本里有历史注释和乱码或者建库语句的字符集和当前MySQL不一致。排查时用数据库常用命令show table status查看表的字符集desc user看字段类型“中文变成问号”的问题绝大多数出在字符集。4.3 DBUtil与增删改查PreparedStatement是底线数据库连接不能每个Servlet写一遍抽取一个DBUtil是标准做法网上订餐项目里最典型的实现长这样public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/order_db ?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.jdbc.Driver); // MySQL 5.7驱动 } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 从里到外依次关闭每次先判空避免一堆空指针 } }逻辑说明连接串里的useUnicodetrue和characterEncodingutf8解决页面中文存库乱码serverTimezoneAsia/Shanghai是MySQL 8.0驱动要求不写会报时区异常。如果数据库是8.0驱动类名要改成com.mysql.cj.jdbc.Driverjar包也必须换成8.0.x。参数说明PASSWORD要改成本机MySQL的实际密码项目里常常出现代码里一个密码、本机一个密码部署时反复报Access denied排错半天。查询和更新统一走PreparedStatement// userDao.findById PreparedStatement ps conn.prepareStatement(SELECT * FROM user WHERE id ?); ps.setInt(1, id); ResultSet rs ps.executeQuery();逻辑说明SQL里所有参数用问号占位再用set方法赋值不要用字符串拼接。JSP项目里最常见的低级错误是把用户输入直接拼进SQL登录接口被人传一个 or 11就能绕过校验。ResultSet转实体对象要抽一个rowToUser方法表字段变化只改这一处增删改查的通用模板也就固定了。executeUpdate的返回值表示影响的行数insert返回主键要单独用getGeneratedKeys处理这在订单插入时经常用到。5. 部署避坑与常见问题从源码到能演示的最后一公里代码部署这个环节很多人不是被业务逻辑难住而是被环境问题耗掉两三天。下面这几条踩坑记录是按出现频率排的现象、原因、解决三连照着排查能省下不少时间。5.1 Tomcat版本不匹配项目红叉加404现象源码导入IDE后项目一堆红叉或者Tomcat能启动但页面总是404控制台里报出javax.servlet无法解析的编译错误。这类报错在执行了反复清理、重新发布之后依然存在很容易让人误判成代码损坏。原因本机装的Tomcat 10把javax.servlet整个包迁移到了jakarta.servlet而这份项目源码里的import javax.servlet开头的代码已经无法在新版本里编译。版本不匹配是典型的部署阶段问题和业务代码没有关系。解决再装一个Tomcat 8.5或9.0在IDE里把项目的Target Runtime切换到新装的这个版本然后执行clean并重新发布。不要试图全局替换import包名老项目还依赖其他Servlet API相关类库只换包名容易漏伤筋骨。换完版本后浏览器强制刷新一次历史缓存的404页面会骗你继续排查。5.2 中文乱码请求、Tomcat、数据库三层都要管现象用户注册后界面回显名字正常但打开数据库看username是问号或者页面提交中文后重定向回显变成乱码。乱码问题在不同浏览器下表现还不一样Chrome正常、IE乱码的情况也见过这更让人摸不着头脑。原因有两层。第一层是Tomcat接收请求时的编码不对post请求没有setCharacterEncoding(UTF-8)get请求的中文参数走的是URI编码第二层是MySQL建库建表字符集不对表还是默认的latin1中文自然存不进去。解决先在web.xml里配一个CharacterEncodingFilter对所有请求统一设置UTF-8再把数据库连接串加上characterEncodingutf8最后在MySQL里alter table user convert to character set utf8mb4。get请求的中文乱码比较阴间Filter管不到Tomcat的URI解析层要在Tomcat的server.xml里给Connector加URIEncodingUTF-8。这个点经常被忽略改了半天Filterget传中文还是乱码。5.3 数据库连接失败驱动、密码、URL三件事分开看现象点击登录按钮直接500控制台出现三种典型报错ClassNotFoundException、Access denied for user或Communications link failure。这三种报错长得很像新手容易一直盯着其中一种排查结果方向跑偏。原因三种报错对应三个不同层次。ClassNotFoundException是驱动jar包没放进WEB-INF/lib或驱动类名写错Access denied是DBUtil里的密码和本机MySQL密码不一致Communications link failure是端口、IP或库名不对先查一下3306是否被MySQL占用。解决按驱动类、密码、URL顺序排查不要跳步。确认驱动jar和数据库大版本匹配MySQL 5.7配5.1.x驱动MySQL 8.0配8.0.x驱动。MySQL 8.0还会遇到Public Key Retrieval is not allowed这是连接串少了allowPublicKeyRetrievaltrue加上之后重试即可。每次改完配置都要重启TomcatJDBC连接串只在应用启动时读取一次。5.4 404、405、500三类报错的定位套路现象页面点击后不是跳到目标页而是固定三种表现404表示URL不存在405表示请求方法不支持500是后台代码抛异常。这三种状态码在浏览器F12的Network面板里一眼就能分辨。原因404先核对web.xml里Servlet映射的url-pattern和前端表单action是否完全一致再看JSP文件是否真的在webapp目录下有人改过目录名导致路径错位。405一般是Servlet只写了doGet没写doPost前端表单却用post提交。500要找控制台堆栈最常见的NullPointerException是数据库没查到数据但代码直接调用了结果集。解决把web.xml所有servlet-mapping列出来和前端一份份核对405补doPost或统一用service入口分发500打开日志定位到具体行号再改代码。这三类错误占部署调试期八成问题养成“先看控制台第一行异常再动手”的习惯比乱试路径靠谱得多。真正难的不是某一个错误而是同时出现两三个错误时按状态码一层层剥问题就会收敛。6. 从项目报告到答辩演示让交付物串成一条证据链6.1 项目报告和答辩PPT的对应关系项目报告的核心不是堆文字而是把需求分析、数据库设计、功能实现、测试部署这条线对齐到源码上。PPT每页只讲一个模块放一张关键截图加三行要点详细内容留给报告。报告里的数据库设计章节直接放第4章的表结构和关系说明功能实现章节的每个代码片段都标上文件路径答辩时老师指到哪你能翻到对应位置。源码管理建议用Git在本地建仓库每完成一个模块提交一次写开发日志时时间线也有了。6.2 演示路径先走用户流程再切管理端演示顺序固定成一条业务链打开首页登录浏览分类菜品加入购物车提交订单切到管理端查看订单并更新状态再回到前台看订单状态变化。整个过程控制在八到十分钟不演示边角功能。管理端重点演示菜品上下架和库存变化这两个操作在用户端能立刻看到效果最能体现系统闭环。如果现场网络或浏览器出了问题把之前录好的部署视频作为兜底直接播放备好的录屏不冷场。6.3 截图与部署视频的录制顺序截图按“系统首页、登录、菜品列表、购物车、提交订单、后台订单管理、菜品增删改查”这个顺序截每张图对应PPT一页。部署视频不要从环境搭建开始录直接从启动Tomcat、导入SQL、打开浏览器访问首页开始控制在三分钟以内。录制前把浏览器缩放固定到100%页面字号统一很多截图失败都是因为缩放比例不一致导致文字发虚。前年带过一个学弟做类似方向的题目他花了两周折腾环境后来我发现他只是把Tomcat 10当成8.5在配换掉之后一小时就能跑了。做毕设最大的教训不是代码写不对而是环境版本没确认就往下走遇到问题又不按层排查。这套JSP网上订餐系统的每个坑基本都有成熟解法希望帮到你。本文还有配套的精品资源点击获取