
简介一份基于JSP的网上订餐管理系统毕业设计资料包面向计算机相关专业学生用于完成课程设计、毕业设计或项目实战。资源涵盖前端用户界面、后台管理功能及数据库交互完整呈现需求分析、系统设计、编码实现到测试维护的软件工程全流程重点展示JSP与Servlet协同处理HTTP请求的业务逻辑包括订单、菜品的动态交互同时讲解关系型数据库如MySQL中的ER模型、表结构设计、主外键关联及SQL查询优化。压缩包约95.81MB内含项目报告、答辩PPT、源代码、数据库文件、系统截图和部署视频其中部署视频详细演示Tomcat环境配置、数据库安装以及Web应用发布过程可对照视频完成本地环境搭建与运行。已有919人学习适合希望系统掌握Web开发全流程、提升实践能力的毕业生和自学爱好者。1. jsp网上订餐管理系统到底是什么毕业设计里的标准电商骨架凌晨两点一个学生对着裂开的菜品图片发呆第二天老师就要检查订单表和菜品表怎么关联。这种画面在每年的毕业设计季反复出现。基于jsp的网上订餐管理系统是Java Web课程设计和毕业设计里最常见的一类选题用户注册登录、浏览菜品、加购物车、提交订单管理员维护分类、菜品、处理订单技术栈就是jspservletmysqltomcat这套传统组合。它要解决的核心问题不是外卖业务本身而是让你在没有Spring Boot加持的情况下完整走一遍“页面→控制器→数据库”的闭环。这套方案适合学完Java Web但没做过完整项目的人也适合想拿现成骨架替换需求的人。值得投入因为它每个环节都看得见、能复现、答辩问得住。2. 先想清楚再动手jsp网上订餐管理系统的技术选型与模块划分2.1 为什么还选jspservlet毕业设计里的技术保守主义这几年我见到的网上订餐系统毕设技术栈大概分两派。一派用Spring BootMyBatis页面配Vue看着新潮但从写代码到部署踩坑点比jsp方案多一倍。另一派就是jspservletmysql丑但稳。你拿到的这个标题就是后者。做毕设不是做生产系统评价标准是“答辩时说得清楚、演示时不翻车、复试时老师能看懂”。jsp和servlet是你的Java Web课程里反复出现的组合老师问请求怎么到Servlet、JSP怎么取数据你都能指着代码讲。这是技术保守主义不是技术落后。分工也固定jsp负责渲染页面servlet负责接收请求和跳转DAO类负责SQLmysql存数据。有的同学把业务逻辑全写在JSP里比如在% %里写大段Java页面和逻辑混在一起这是传统jsp项目最常见的坏味道。我一般会让jsp只做展示和表单提交把登录校验、订单计算这类逻辑放到servlet或service里后面改需求时不用翻页面。这一节还能回答老师的第一个问题“为什么不用更流行的框架”你只要说清楚jspservlet能更直观地演示HTTP请求-响应过程比一句“课程要求”更有说服力。如果老师追问再补一句servlet是Java Web的基石Spring Boot里的DispatcherServlet本质上和servlet是同一套机制。这句话能把“保守”变成“基础扎实”。2.2 用户端、管理端的模块边界先画图再写代码网上订餐系统听上去只是点餐但模块一定要拆清楚。最常见的拆法是两层用户端能注册登录、浏览分类和菜品、查看菜品详情、管理购物车、提交订单、查看自己的订单管理端负责菜品分类、菜品信息、订单状态流转比如把“待处理”改成“配送中”再到“已完成”。有些版本会加一个商家角色但大多数毕设把商家合并到管理端里避免权限模型复杂化。我建议你就按用户和管理员两个角色做角色少一个答辩复杂度降一半。功能划分可以直接做成一张表这也是项目报告里“功能需求”那一节的内容模块功能点涉及表用户注册、登录、个人信息展示页面user用户按分类浏览菜品、查看详情category, food用户添加购物车、修改数量、提交订单cart, orders, order_item用户查看我的订单、取消未处理订单orders, order_item管理端分类增删改查、菜品增删改查与上下架category, food管理端订单查询、修改订单状态orders表建好、功能列好再动手写代码。很多翻车现场是反过来的先写登录再写页面最后发现购物车和订单的关系没想清楚又回头改表结构。功能清单还能用来对答案做一步勾一步答辩前检查哪些功能漏了。单子上的每一项最终都要能对应到页面入口和servlet映射不然老师说“你这个系统怎么没有这个功能”时你只能现场解释那很狼狈。2.3 数据库表设计7张表与字段取舍订餐系统的数据库是整个项目的根。表设计错了后面的增删改查全在跟错误结构较劲。常见做法是建七张表管理员表admin、用户表user、分类表category、菜品表food、购物车表cart、订单主表orders、订单明细表order_item。其中orders和order_item是一对多关系一张订单包含多个菜品所以主表存订单总价和状态明细表存每个菜品的单价和数量。这是数据库设计里必考的点答辩时老师大概率会问。核心建表语句我习惯这么写create table user ( id int primary key auto_increment comment 用户id, username varchar(50) not null unique comment 登录名, password varchar(64) not null comment 密码建议存md5或sha1后的值, phone varchar(20) default null comment 联系电话, address varchar(200) default null comment 送餐地址, create_time datetime default current_timestamp comment 注册时间 ) engineinnodb default charsetutf8mb4; create table category ( id int primary key auto_increment, name varchar(50) not null, sort int default 0 comment 排序小的在前 ) engineinnodb default charsetutf8mb4; create table food ( id int primary key auto_increment, category_id int not null comment 关联category.id, name varchar(100) not null, price decimal(10,2) not null comment 单价用decimal不用float, image varchar(200) default null comment 图片路径, description varchar(500) default null, status tinyint default 1 comment 1上架 0下架 ) engineinnodb default charsetutf8mb4;订单两张表的字段再单独说。order_item里的price要冗余一份存下单那一刻的菜品单价不能下单后菜品改价历史订单的金额也跟着变。这是很多第一次做系统的人想不到的细节create table orders ( id int primary key auto_increment, user_id int not null comment 关联user.id, total_price decimal(10,2) not null comment 订单总价, status tinyint default 0 comment 0待处理 1已接单 2配送中 3已完成 4已取消, remark varchar(200) default null comment 用户备注, create_time datetime default current_timestamp ) engineinnodb default charsetutf8mb4; create table order_item ( id int primary key auto_increment, order_id int not null, food_id int not null, food_name varchar(100) not null comment 冗余菜品名, price decimal(10,2) not null comment 下单时单价, quantity int not null default 1 ) engineinnodb default charsetutf8mb4;几个参数取舍说一下密码字段用64位是因为常见哈希算法输出是固定长度留50不够价格用decimal(10,2)而不用float是因为float有精度误差算总价时可能多一分少一分时间字段用datetime default current_timestamp插入时不用手写时间。字符集统一用utf8mb4不要用utf8否则用户备注里出现一个生僻字或表情符号整条SQL直接报错。关联字段我建议只建逻辑关联不建物理外键因为外键会让菜品删除受限演示时明明想删个分类却报错很尴尬表关系写在项目报告里答辩讲逻辑关联即可。2.4 URL与页面规划一张映射表避免写飘代码量上来之后最怕的是URL混乱。最常见的问题是表单action写错路径点了提交之后404。我一般先规划好servlet的url-pattern再写页面。用注解方式的话在servlet类上写WebServlet即可用web.xml配置的话注意路径必须带/开头。下表是毕设里常用的一套规划请求路径作用对应Servlet/user/register用户注册UserServlet?actionregister/user/login登录UserServlet?actionlogin/user/logout退出UserServlet?actionlogout/food/list菜品分页列表FoodServlet?actionlist/cart/add加购物车CartServlet?actionadd/cart/list购物车页面CartServlet?actionlist/order/submit提交订单OrderServlet?actionsubmit/admin/food/list后台菜品管理AdminFoodServlet?actionlist这套路径的规律是同一个业务实体的所有操作共用一个servlet用action参数区分。好处是web.xml里配置项少缺点是每个servlet里要写一个if/else分派。分派代码很短但一定要统一写在doGet或doPost的开头别分散到两个方法里。页面里的所有跳转和表单action都写成绝对路径比如${pageContext.request.contextPath}/food/list不要写相对路径。这个习惯能省掉后面一半的404尤其当项目部署到Tomcat后带一层上下文路径时相对路径会整体错位。3. 从登录到下单jsp网上订餐系统核心代码怎么落3.1 用Filter做登录校验拦截器比在每个页面判断省事很多入门项目把登录校验写在每个JSP页面顶部拿session判断一遍几十个页面就写几十遍。这种做法能跑但代码又臭又长而且容易漏。更常见的做法是用一个Filter统一拦。下面这段是登录拦截的最小实现public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); String uri request.getRequestURI(); // 白名单登录注册和静态资源直接放行 if (uri.endsWith(login.jsp) || uri.endsWith(register.jsp) || uri.contains(/css/) || uri.contains(/js/) || uri.endsWith(user/login) || uri.endsWith(user/register)) { chain.doFilter(req, resp); return; } if (session null || session.getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }逻辑说明先判断当前请求是不是登录、注册、静态资源这几个白名单路径是就直接放行否则检查session。session没有或者里面没有user对象就跳回登录页。关键点有两个getSession(false)在session不存在时返回null不会自动创建一个新sessionuri判断建议用endsWith而不是contains防止把/user/loginSave这种路径也误放行。Filter配置在web.xml里映射路径写/*和Servlet映射互不冲突。这段代码做完你就能跟老师讲“权限控制放在了统一入口”而不是散落在每个页面。管理端可以再单独加一个AdminFilter或者在这个Filter里判断uri是否以/admin开头再检查session里的admin属性。两个角色的权限混在一个Filter里判断时不要把用户和管理员的判断写成同一个逻辑否则会出现普通用户访问后台也能放行的问题。答辩时老师很爱问“你怎么防止用户直接访问admin页面”你就把这段Filter贴给他看再补一句“后台路径单独走AdminFilter校验”。3.2 菜品列表与分页servlet取数、jsp渲染菜品页是订餐系统里访问量最大的页面也是分页必考的地方。分页逻辑不复杂核心就两条SQL一条查总数算总页数一条查当前页数据。用刚才表里的food表来写servlet里这样取数int pageNo 1; int pageSize 8; try { pageNo Integer.parseInt(request.getParameter(pageNo)); } catch (Exception e) { pageNo 1; // 参数为空或非法时回退到第一页 } int start (pageNo - 1) * pageSize; String countSql select count(*) from food where status 1; String pageSql select * from food where status 1 order by id desc limit ?, ?; PreparedStatement psCount conn.prepareStatement(countSql); ResultSet rs psCount.executeQuery(); rs.next(); int totalCount rs.getInt(1); int totalPage (totalCount pageSize - 1) / pageSize; PreparedStatement psPage conn.prepareStatement(pageSql); psPage.setInt(1, start); psPage.setInt(2, pageSize); ResultSet rsPage psPage.executeQuery(); ListFood foodList new ArrayListFood(); while (rsPage.next()) { // 逐行封装成Food对象再放进request转发给jsp } request.setAttribute(foodList, foodList); request.setAttribute(totalPage, totalPage); request.setAttribute(pageNo, pageNo); request.getRequestDispatcher(/food_list.jsp).forward(request, response);参数说明pageSize设8一页显示8个菜品演示时不需要翻太多次节奏刚好limit的偏移量是(pageNo-1)*pageSize这是最容易写错的地方第一页偏移0第二页偏移8。总数查出来算总页数用(totalCountpageSize-1)/pageSize这是向上取整比用Math.ceil省事。JSP端用JSTL的c:forEach遍历foodList循环里渲染菜品图和价格页脚放上一页下一页链接链接里带pageNo参数。分页做完你就能顺带讲清楚“为什么JDBC要查两次数据库”答辩时是加分项。3.3 购物车与订单提交session临时态与数据库事务购物车有两种实现选择一种是存数据库cart表用户没点结算也能查另一种是存session关浏览器就丢。毕设建议用session存逻辑简单、不用清理脏数据而且答辩时能讲清“session用于保存会话状态”。总价在购物车页面实时算提交订单时再算一次不要直接把页面上显示的合计金额信过来否则用户改一下浏览器参数订单金额就错了。这是一个容易被忽视的漏网之鱼。提交订单是整个系统里唯一要求原子性的操作先插订单主表再插订单明细表。如果第一条成功第二条失败会出现一个没有明细的空订单。用JDBC事务可以这样处理conn.setAutoCommit(false); try { PreparedStatement psOrder conn.prepareStatement( insert into orders(user_id, total_price, status) values(?, ?, 0), Statement.RETURN_GENERATED_KEYS); // 设置参数后执行 ResultSet keys psOrder.getGeneratedKeys(); int orderId 0; if (keys.next()) { orderId keys.getInt(1); } PreparedStatement psItem conn.prepareStatement( insert into order_item(order_id, food_id, food_name, price, quantity) values(?, ?, ?, ?, ?)); // 循环把购物车每个条目set进去订单明细表的order_id用上面拿到的orderId conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); }逻辑说明setAutoCommit(false)之后commit之前的所有SQL都不会真正落库执行完主表和明细表两条插入后commit任何一步抛异常就rollback。getGeneratedKeys()拿的是自增主键注意prepareStatement第二个参数要传Statement.RETURN_GENERATED_KEYS否则拿不到。订单状态在插入时写死0对应表设计里的“待处理”。这段代码毕业后写其他系统也会用到事务的学习价值比页面高得多。3.4 后台管理菜品增删改查与订单状态流转后台管理就是把数据库增删改查包一圈页面。以菜品管理为例一个AdminFoodServlet配合action参数区分list、add、update、delete四个操作。删除菜品用物理删除还是逻辑删除我更建议逻辑删除把food表的status字段改成0页面上只查status1这样删掉的菜还有订单记录引用着不会破坏历史订单。物理删除一个菜老的订单明细里food_id瞬间悬空如果明细表没冗余food_name字段订单页面直接显示不出菜名。订单状态流转是后台最核心的功能。常见做法是写一个更新接口update orders set status ? where id ?状态值从0到4按0待处理、1已接单、2配送中、3已完成、4已取消的顺序流转。页面用下拉框或按钮提交后台只做一件事校验当前状态是否能跳到目标状态。比如已取消的订单不能再变成配送中。校验代码不复杂但一能防止乱改二能跟老师讲“状态机”概念。菜品图片上传建议用commons-fileupload组件上传后把图片路径写进food.image再配合第5章的虚拟路径映射这块坑最多。4. 把项目跑起来idea新建jsp项目到Tomcat部署的全流程4.1 环境搭配JDK、Tomcat、MySQL版本怎么配不翻车网上订餐系统是传统jsp项目环境组合我建议固定成一套别追新。JDK用1.8Tomcat用8.5或9.0MySQL用5.7或8.0IDE用IDEA。为什么不建议JDK 17很多老项目依赖的jar和JSP编译器在JDK 11以后有兼容问题报错又偏毕设时间经不起折腾。Tomcat 10也要避开Tomcat 10把javax.servlet包改名成jakarta.servlet老代码导入的javax.servlet.*全部编译不过这种坑只能重写或降级。四个环境的组合是个闭合方案IDEA里配置一个Tomcat8.5作为运行服务器项目以war exploded方式部署数据库用Navicat或命令行导入SQL。如果在自己电脑上跑MySQL版本和本机一致即可如果拷给别人演示最好连MySQL版本也保持一致因为mysqldump导出和导入在5.7与8.0之间偶尔有差异。环境这步最快的验证方式是启动Tomcat后访问localhost:8080能看到Tomcat默认页再用命令行mysql -u root -p能进数据库这两点通了再谈代码。4.2 IDEA新建jsp项目并导入源码骨架结构与lib目录拿到一套源码先看目录结构这一步能判断项目靠不靠谱。传统jsp项目不是Maven结构而是标准的Web应用目录order_system/ src/ com/order/ # servlet、dao、model、filter都在这 db.properties web/ WEB-INF/ web.xml lib/ # mysql驱动、jstl等jar包 classes/ css/ js/ images/ login.jsp register.jsp index.jsp food_list.jsp cart.jsp order_list.jsp admin/ food_list.jsp order_list.jsp在IDEA里导入时一般选New Project把src设为Sources Root把web目录设为Web Resource Directory然后在Project Structure里添加Web Facet。如果项目已经带.ideaIDEA会直接打开如果没有按上面结构手工配一次也就十分钟。注意WEB-INF/lib里的jar包不能被Tomcat的lib替代比如mysql-connector、jstl一定要放项目里而servlet-api.jar不要放它由Tomcat提供放了可能出现版本冲突。IDEA里新建jsp项目有个习惯操作是右键web目录新建jsp然后IDEA在web/WEB-INF下自动生成web.xml。如果你是新建空白项目照着源码重建别忘了把Filter、Servlet映射都写进web.xml或注解里。web.xml的schema版本如果是3.1Tomcat8.5能认如果写成2.3的老格式注解方式会不生效页面半天找不到Servlet。先看web.xml的 标签版本再决定用注解还是配置这是很多人忽视的一步。4.3 数据库初始化执行sql脚本与修改连接配置数据库初始化分两步建库导数据改连接配置。第一步在Navicat里新建数据库order_system字符集选utf8mb4然后运行项目里的.sql脚本。脚本里如果只有建表语句自己再补几条测试数据如果有insert执行后先查一下food表有没有数据避免演示时页面空荡荡。第二步是修改数据库连接配置传统jsp项目一般写在src下的db.properties或DBUtil.java里内容大致是这样jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/order_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456参数说明driver是MySQL8的驱动类名如果项目用的MySQL5.7这个类名也可以用老项目里的com.mysql.jdbc.Driver在MySQL8下会警告但不一定报错建议统一改成cj版本characterEncodingutf8是告诉驱动用UTF-8传字符串和数据库表字符集utf8mb4配合中文不乱码serverTimezone必须设置MySQL8的驱动不写这个参数会直接报时区错误。改完配置重启Tomcat能登录能查到菜品说明数据库这环通了。有些项目用的是DBCP或C3P0连接池配置文件在META-INF/context.xml里重点关注maxActive和maxIdle默认值一般够用。4.4 打包war并部署到Tomcat两种方式的区别部署方式有两种一种是在IDEA里直接跑另一种是打包成war丢到Tomcat。答辩现场建议用第一种代码改完点一下就能重启省去反复打包的时间项目报告里建议写第二种显得正式。如果你要把系统拷到另一台电脑跑war方式最省事。war包的产出在IDEA里叫Build Artifact先配置Artifact类型选Web Application Exploded或Archive。也可以用命令打传统jsp项目打包war时注意在web目录下执行cd /path/to/order_system/web jar cvf ../order_system.war .这条命令把当前目录下所有内容打进war包war包格式和web目录结构一样WEB-INF在根下。打完后把这个war文件复制到Tomcat的webapps目录启动Tomcat它会自动解压。访问路径是http://localhost:8080/order_system/注意这个上下文名默认就是war文件名如果你之前配置了JSP里相对路径会出现403或404。若想换个访问名把war包改名即可比如改成order.war访问路径跟着变。有人问nginx能不能部署jsp答案是nginx本身不能解析jsp它只管静态文件和反向代理动态请求要转发给Tomcat处理。毕设演示一台Tomcat就够了不用上nginx。如果项目报告里写了“部署到云服务器”买一台Linux装好JDK和Tomcat把war上传到webapps和本地流程一模一样只是连接数据库要改成本机IP。部署视频录到“Tomcat启动、系统首页打开”这一帧就可以收尾了。5. jsp网上订餐系统避坑指南5个让人反复返工的常见问题5.1 乱码问题UTF-8配了还是乱码先查三个地方现象注册一个用户名登录页显示名字全是问号或者后台菜品名称乱码。原因很少是一个点通常是三个环节不一致JSP页面本身的编码、Servlet接收请求参数的编码、数据库连接URL里的编码。解决方法是把三处全部统一成UTF-8。页面头写全% page contentTypetext/html;charsetUTF-8 languagejava %HTML的meta标签也写UTF-8。最关键的是在web.xml里配置一个字符编码Filterpublic class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); chain.doFilter(req, resp); } }filter filter-nameencoding/filter-name filter-classcom.order.filter.EncodingFilter/filter-class /filter filter-mapping filter-nameencoding/filter-name url-pattern/*/url-pattern /filter-mapping逻辑说明request.setCharacterEncoding(UTF-8)要放在doFilter第一行它只对POST请求的body有效GET请求参数乱码要靠Tomcat的URIEncoding一般Tomcat8.5默认已经UTF-8。注意这个Filter必须是所有Filter里最先执行的那个登录Filter排在它后面否则POST请求的body已经被先读走再setCharacterEncoding就晚了这是很多人配了Filter还是乱码的真正原因。数据库连接URL里characterEncodingutf8也不要落下三处统一后乱码基本绝迹。5.2 数据库连接失败驱动版本和时区参数最容易被忽略现象Tomcat启动正常页面一查数据就报ClassNotFoundException或者Communications link failure。ClassNotFoundException十有八九是mysql驱动jar没放进WEB-INF/lib或者放的版本太老。Communications link failure常见原因是MySQL服务没启动、端口不是3306、账号密码不对。还有两类和版本绑定的问题MySQL8的驱动类名应该写com.mysql.cj.jdbc.Driver老代码写com.mysql.jdbc.Driver虽然能跑但满屏弃用警告URL里没写serverTimezoneMySQL8直接报The server time zone value错误。解决方式就是把第4章那段db.properties原样改一遍重点检查driver和serverTimezone。排查顺序也有讲究先用命令行mysql -u root -p试一次确认MySQL本身能进再用一个小测试类连接一次确认jar和URL写对最后才启动Tomcat点页面。很多同学跳过了前两步直接看Tomcat日志日志里报的错叠加了多层原因反而定位不准。还有一个隐蔽坑数据库密码里有特殊字符比如或:直接拼在URL里会把jdbc格式搅乱这时候改用连接池配置的property方式传用户名密码不要在URL里拼。密码里有没有特殊符号这个问题在部署视频里值得专门说一句。5.3 404或500访问路径与部署上下文的关系现象首页能打开点登录或提交订单就404或者页面能打开但后台管理菜单全部500。404基本是路径问题分两种表单action写成了相对路径比如actionfood/list部署到Tomcat后上下文变成/order_system实际请求被解析成/order_system/food/list而不是预期的路径于是找不到Servlet另一种是web.xml里Servlet映射漏配或url-pattern写错。解决方式统一用绝对路径JSP页面里写${pageContext.request.contextPath}拼前缀。500大多是空指针或SQL语法错误最常见的是session里取不到user比如登录Filter放行了某条路径结果页面以为用户一定登录了直接调用user.getUsername()就炸了。看错误日志也有技巧。IDEA控制台只能看到一部分完整日志在Tomcat的logs目录文件名是localhost.2025-04-01.log这种按日期命名的文件用文本编辑器打开搜Caused by那才是真正的根因。Tomcat9之后的日志格式变了但Caused by和at com.order.xxx的行号定位仍然好用。500页面里如果直接看到整屏的异常堆栈说明项目在开发模式答辩前最好把错误提示统一改成“操作失败”或“系统异常”不然老师看到一堆英文异常会觉得不稳。5.4 菜品图片上传后不显示Tomcat虚拟路径映射现象admin后台能上传图片显示上传成功但刷新前台列表图片位置一片空白。原因项目把图片保存到了项目部署目录下的upload文件夹比如Tomcat的webapps/order_system/uploadIDEA重新部署时这个目录被清空图片就没了或者图片存到了磁盘任意位置浏览器访问不到绝对路径。解决方式是配置Tomcat的虚拟路径映射让磁盘上的upload目录通过http://localhost:8080/upload/访问。在server.xml的Host节点里加Context path/upload docBaseD:/order_system_upload reloadabletrue /这样上传代码里把文件写到D:/order_system_upload页面里的image字段存/upload/xxx.jpg前端img标签就能按URL访问。注意生产环境不要把docBase放到Tomcat的webapps下面否则重启会被覆盖。部署视频录制时最好演示一次“上传图片后刷新仍然显示”这个细节能证明你处理了真实问题。顺手把上传大小限制调一下在web.xml里给servlet配multipart-config的max-file-size或者用commons-fileupload的setFileSizeMax不然上传一张手机原图直接报超限。5.5 数据库连接没关闭too many connections与页面卡死现象系统用一会儿后页面变慢最终报Too many connections或Connection is not available。原因每个DAO方法里新建了连接但用完没close连接池里的连接被耗尽新的请求只能排队等超时。解决方式是在finally块里关ResultSet、PreparedStatement、Connection顺序从里到外而且要判断非空。conn.close()虽然会间接释放statement但如果在大循环里开了多个statement最好各自关掉。写一个BaseDao统一封装关闭方法能减少重复代码finally { if (rs ! null) { try { rs.close(); } catch (SQLException e) {} } if (ps ! null) { try { ps.close(); } catch (SQLException e) {} } if (conn ! null) { try { conn.close(); } catch (SQLException e) {} } }这个问题的隐蔽点在于单机演示时连接用完不关可能半小时都不爆可答辩现场连着投影、多次刷新、演示完又打开后台连接一下子就堆上去了。提前把连接池的maxActive设小一点比如10能让问题提前暴露而不是在答辩时爆发。另一个相关习惯是查询类操作不要手动开启事务只有订单提交那种多表写入才需要setAutoCommit(false)普通查询保持自动提交能少出很多连接锁问题。6. 答辩前一周怎么把素材变成答辩稿项目报告、PPT顺序与部署视频脚本拿到这套jsp网上订餐管理系统的材料包别急着把代码从头读一遍。它的价值不在代码量而在资源组合项目报告讲设计答辩PPT讲思路源代码和数据库是实锤截图和部署视频是证据。我会按三个产出挨个处理。项目报告先画ER图和用例图ER图里把user、category、food、orders、order_item五张表的关系标清楚订单和菜品的多对多关系要借助order_item这张中间表体现这是我当年吃过亏的地方。功能需求直接抄第2章那张功能表每个功能写一句“输入-处理-输出”字数够且经得起问。数据库设计里把status字段的含义做成状态说明表老师问起状态机也有东西可指。答辩PPT顺序我固定用六页背景与意义、技术栈、数据库设计、功能演示截图、核心代码讲解、收获与展望。注意核心代码讲解不要贴大段代码贴登录Filter和订单事务这两处一个讲权限控制一个讲数据一致性都是高分点。部署视频脚本按“启动MySQL、导入数据、启动Tomcat、打开首页、用户下单、后台接单、数据库里看到订单记录”这样的顺序录。最后一帧把mysql命令行里select * from orders的结果拍进去让老师看到下单是真的写进了数据库。我自己做毕设那会儿最吃亏的是只贴代码不解释表关系被一句“订单明细为什么单独一张表”问住了。后来养成的习惯是演示任何功能前先在数据库里查一遍对应表的记录变化让页面和库对应起来。你如果时间紧也先把这条链路理顺比多背二十行代码有用。希望帮到你。本文还有配套的精品资源点击获取