JavaWeb实战:Java+JSP+MySQL网上订餐系统源码部署与改造
发布时间:2026/9/28 6:01:20来源:尧图网络
简介这是一份基于JavaJSPMySQL实现网上订餐系统的完整源码包适合Java Web初学者、课程设计或毕业设计参考。源码覆盖用户登录注册、菜单展示、购物车与订单提交等核心流程采用JSP构建视图层、Servlet处理业务请求、MySQL完成数据持久化并融入了MVC分层设计思想。压缩包共1262个文件、47.38MB以css样式、png/jpg图片、jsp页面、java源文件、class文件、jar依赖库、xml配置和sql脚本为主附带数据库初始化脚本222.sql以及Eclipse/IntelliJ IDEA的工程配置便于直接导入和调试。已有384人学习下载有助于从项目结构、前后端交互和数据库设计中快速掌握传统Java Web开发的基础实践。1. 一个能跑的javajspmysql网上订餐系统源码先别急着删做Java课程设计或者毕设选题时“网上订餐系统”是出现频率最高的题目之一。这套基于javajspmysql的简单网上订餐系统源码不是那种一行都跑不动的半成品而是标准的ServletJSP三层JavaWeb项目用户端有注册登录、菜品浏览、购物车、下单后台有菜品管理、订单管理、用户管理业务闭环完整。它没有Spring Boot把一切封装成黑匣子代码量适中正适合想搞清楚JavaWeb请求链路的人。对准备交课程设计、应付答辩、或者想顺着java面试题里的Servlet生命周期去深挖的读者这套源码是很好的练手对象。这篇笔记会把版本配平、数据库初始化、核心代码、常见坑和扩展方向讲透让源码在你电脑上真正跑起来。2. 先看懂这套源码的底子JSPServletMySQL的请求链路与三层结构2.1 还在用JSP不是落后课程设计选型和面试考点的双重原因很多第一次下载这套源码的人会问现在都Spring Boot了为什么课程设计还在用JSP答案不复杂高校的java课程设计案例源码里JSPServlet依然是教学主力。它把请求处理、页面渲染、数据库访问拆成三层每一层都肉眼可见适合考核java基础掌握程度。Spring Boot固然好但自动配置把很多东西变成了黑匣子答辩时老师一问DispatcherServlet和Servlet的区别答不上来反而扣分。更深一层java面试题里Servlet生命周期、Session原理、Filter拦截器这些考点恰恰只有在这种老式JSP项目里能直接看到。用框架时你只是在调用用JSPServlet时你是在写它们本身。所以这套源码不是过时而是“教学友好”。它比你在IDEA新建jsp项目后从零敲代码省事得多也比直接抄一个Spring Boot全家桶更贴近课程考核点。对jsp入门阶段的人先用这种项目把请求链路摸清楚后面学框架才不会一头雾水。2.2 跟一条请求的流转从浏览器地址栏到MySQL再回JSP页面拿到源码后别急着点运行先把一条请求跟一遍。用户在浏览器输入 http://localhost:8080/ordering/food/list Tomcat收到后先按web.xml里的Servlet映射或WebServlet注解找到对应的Servlet类。Servlet调DAO层查数据库把结果放进request或session最后转发或重定向到JSP页面。JSP做的是渲染用EL表达式和JSTL标签把数据铺到HTML里再返回给浏览器。JSP的机制值得多说一句它第一次被访问时Tomcat会把JSP翻译成一个Servlet源码编译成class后再执行。这也是为什么JSP页面修改后需要重新部署才生效的原因。一个规范的项目里JSP里不应该塞满% %脚本片段而是用标签库取数据。这套源码如果写法规范你会看到类似这样的页面片段% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % body c:forEach items${foodList} varfood div classfood-card span${food.name}/span span${food.price}/span a href${pageContext.request.contextPath}/cart/add?foodId${food.id}加入购物车/a /div /c:forEach /body这段JSP里有几个固定写法要认识。${food.name}是EL表达式从后端塞进request的foodList里取每个对象的name属性c:forEach是JSTL的循环标签items是集合var是循环变量pageContext.request.contextPath是当前应用的上下文路径页面里所有链接都用它拼前缀避免部署后路径对不上。JSTL和EL是jsp入门的标配大部分课程设计源码都会用看到不认识不要慌记住这三个点就够了。Servlet生命周期在源码里也能实际看到。一个Servlet从init()到service()再到destroy()init只执行一次service每次请求都执行容器负责实例化和销毁。java面试题里反复出现这个考点而这套源码里你能用日志输出看到它真实跑一遍比背八股文扎实。2.3 数据表建模用户、菜品、订单五张表怎么设计这套订餐系统的核心表通常是五张最少也是四张。拿到SQL脚本后先打开看一眼表结构大致如下表名作用关键字段user用户表id, username, password, phone, addressfood菜品表id, name, price, image, description, categoryorders订单主表id, user_id, total_price, create_time, statusorder_item订单明细表id, order_id, food_id, food_name, price, countcart购物车表可选id, user_id, food_id, count订单拆成主表和明细表是这套设计最值得学的地方。一个订单包含多个菜品如果只放一张表每个菜品一行都要重复存用户id、总价、地址冗余严重而且“这个订单是一个整体”的概念会丢失。拆成两张表后orders表存订单整体order_item表存每个菜品的明细行。建表脚本通常长这样CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, create_time DATETIME NOT NULL, status TINYINT NOT NULL DEFAULT 1 ); 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, price DECIMAL(10,2) NOT NULL, count INT NOT NULL, KEY idx_order (order_id) );注意order_item里存了food_name和price这两个冗余字段这是刻意设计菜品价格和名称以后可以改但已下单的记录必须保留下单那一刻的快照否则后台一改价历史订单的金额就跟着变对账会乱。这类细节就是答辩时能说出来的加分点。status字段用TINYINT存数字1待处理、2已接单、3已完成比直接存字符串省空间也方便扩展。如果源码表结构里没有cart表说明购物车存在Session里第四章会细讲两种方案的区别。2.4 拿到源码先扫这三个目录再点运行不迟解压zip后先按这个顺序扫一遍源码结构。第一是src下的Java源码包一般按工具类、数据库访问、控制器分层用IDEA左侧项目树数一下有几个Servlet就能判断系统有哪些功能。第二是web目录下的WEB-INF里面是web.xml这是老式JSP项目的入口配置。Servlet映射、过滤器、欢迎页都在这里加新页面时也要改它。web.xml核心内容一般是web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance version3.1 welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list filter filter-nameLoginFilter/filter-name filter-classcom.ordering.filter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern/*/url-pattern /filter-mapping /web-appwelcome-file-list指定访问根路径时默认打开哪个页面filter-mapping把一个过滤器挂到全站路径上。第三是SQL文件目录一般叫sql或database里面是建库建表脚本。这一眼扫完系统能做什么、代码放哪里、数据库怎么初始化就有了底。很多人在这一步省掉直接点运行结果数据库没导入、驱动没配置报一堆错还以为源码有问题其实只是顺序不对。3. 把源码跑起来JDK、Tomcat、MySQL版本配平与数据库初始化3.1 三件套版本怎么配JDK 8 Tomcat 8.5 MySQL 5.7 的搭配逻辑老式JSP项目对版本敏感最稳的组合是JDK 1.8配Tomcat 8.5配MySQL 5.7。不是新版本不行而是老源码在编译和运行时的坑最少。java环境变量配置如果还没做过先确认JDK装好后JAVA_HOME指向安装目录PATH里追加了%JAVA_HOME%\bin命令行敲java -version能输出版本号再往下走。组件推荐版本原因JDK1.8老代码按JDK8语法编译避免模块化带来的兼容问题Tomcat8.5.x对应Servlet 3.1规范javax.servlet命名空间老代码零改动MySQL5.7与mysql-connector-java 5.1.49驱动长期配合稳定IDEA2019以上Community版也能跑不需要Ultimate数据库客户端Navicat或MySQL Workbench导入SQL、看表结构都方便这里有个血泪经验如果机器上已经装了MySQL 8.0不要急着卸载驱动换成8.0.x版本JDBC连接串加useSSLfalse和allowPublicKeyRetrievaltrue大多数源码也能跑。真正容易翻车的是Tomcat版本比如Tomcat 10把javax.servlet改成jakarta.servlet老项目的import语句全部失效这个问题在5.4节详细说。提示这套源码里引用的mysql-connector-java驱动jar包一般在WEB-INF/lib目录下。如果没有去Maven仓库下载5.1.49版本放进去版本宁老勿新。3.2 在IDEA里导入源码并启动Tomcat一步步配下来导入步骤按顺序做缺一步项目就起不来。第一步File - Open选中解压后的源码目录IDEA会自动识别项目类型。如果打开后没看到Web模块右键项目根目录选Add Framework Support勾选Web Application。第二步打开Project StructureCtrlAltShiftS在Artifacts选项卡里点加号选Web Application: Exploded把项目里的web目录指定为Web Resource Directory。第三步配置TomcatRun - Edit Configurations点加号选Tomcat Server - Local在Server选项卡里选到本机Tomcat安装目录然后在Deployment选项卡点加号添加刚才建的ArtifactApplication context默认是/xxx建议改成/ordering这样访问URL和源码里JSP的上下文路径能对上。最后点运行浏览器访问 http://localhost:8080/ordering 。如果IDEA里没有Tomcat选项先确认Run配置面板的加号菜单里找得到Tomcat Server找不到说明IDEA版本太老或安装的是精简版。Community版也自带Tomcat集成出现“部署成功但页面404”时先别怀疑代码去Tomcat的logs/catalina.out日志看一眼实际报错。这一步的逻辑要搞清楚IDEA不负责编译JSP它只是把项目打成war包或exploded目录交给Tomcat真正跑网站的是Tomcat的catalina进程。理解了这一点后面所有部署类问题都能按“容器日志优先”的思路排查而不是盯着代码反复看。3.3 数据库初始化Navicat导入SQL脚本的两条路按mysql安装配置教程装好MySQL后先确认服务启动。用Navicat连本地MySQL新建一个数据库名字和源码里的连接串保持一致通常是ordering。然后右键数据库选“运行SQL文件”选中源码附带的.sql脚本执行完刷新表列表看到user、food、orders这些表就成功了。不想用图形客户端的话命令行也很快。先建库再导入CREATE DATABASE IF NOT EXISTS ordering DEFAULT CHARACTER SET utf8mb4; USE ordering; SOURCE /绝对路径/ordering.sql;然后验证导入结果SHOW TABLES; SELECT * FROM user LIMIT 5;看到返回记录说明数据表结构和初始数据都导入成功。如果脚本里没有CREATE DATABASE语句说明建库动作要你自己做直接运行脚本会报No database selected。还有一点脚本里如果有DROP TABLE语句执行时会把旧表删掉重建所以千万别在正式数据库上乱跑课程设计脚本本地测试环境无所谓。3.4 数据库连接配置集中在这几处改错一处就全断老式JSP项目的数据库连接配置一般在src目录下的db.properties里或者直接写在DBUtil.java的常量里。找到后改成你自己的数据库用户名密码统一格式jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/ordering?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码对应的DBUtil读取配置的代码通常是这样的public class DBUtil { private static String url; private static String username; private static String password; static { try { // 读取db.properties里的连接参数 Properties props new Properties(); InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties); props.load(in); url props.getProperty(jdbc.url); username props.getProperty(jdbc.username); password props.getProperty(jdbc.password); // 加载驱动类只做一次 Class.forName(props.getProperty(jdbc.driver)); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } // 每次需要连接时调用用完必须关闭 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } }这段代码的逻辑静态代码块在类第一次被加载时执行读配置和加载驱动只做一次后续请求不再重复。getConnection每次返回一个物理连接调用方用完要close。url里的参数每个都有用useUnicode和characterEncoding解决中文乱码useSSLfalse关掉MySQL 8.0的SSL握手警告serverTimezoneAsia/Shanghai是MySQL 8.0的时区要求不写会报时区无法识别的错。如果源码里把连接串写死在代码里而不是properties文件也要同步改这里。4. 核心模块拆解登录、购物车、下单与后台管理的可复用写法4.1 用户登录Servlet从表单取值到Session写入登录模块最容易抄出问题因为涉及请求编码、密码校验、Session写入、跳转方式四个环节。典型的登录Servlet是这样写的WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // POST请求必须先设置编码否则中文参数乱码 req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); UserDao dao new UserDao(); User user dao.findByUsernameAndPassword(username, password); if (user ! null) { // 登录成功用户对象放进session后续请求都能取到 HttpSession session req.getSession(); session.setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /index.jsp); } else { // 登录失败用请求转发带错误提示注意不是重定向 req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } }这段代码有两个面试常考的点藏在里面。一是sendRedirect和forward的区别重定向是浏览器再发一次新请求地址栏会变request里的属性会丢转发是服务器内部直接派发地址栏不变request.setAttribute带过去的值还能读到。二是session和request的生命周期session是会话级别浏览器不关就还在request是单次请求级别用完即销毁。所以登录用户放session错误提示放request不是随便放的。检查源码时重点看DAO层的findByUsernameAndPassword是怎么写的。规范写法是PreparedStatement预编译加参数绑定防止SQL注入如果看到字符串拼接SQL的写法答辩时可以主动指出这个安全改进点。翻一翻user表存密码的字段如果存的是明文也可以作为“我会加盐哈希加密”的改进方向讲。4.2 用Filter拦未登录请求一个拦截器挡掉所有页面购物车、下单、后台管理这些页面没登录绝对不能访问。老项目里最常见的偷懒做法是在每个Servlet里复制粘贴一段session判断更好的写法是一个过滤器配一次URL规则全站生效WebFilter(/*) 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; String uri request.getRequestURI(); // 放行登录注册页、静态资源、首页 if (uri.contains(login) || uri.contains(register) || uri.endsWith(.css) || uri.endsWith(.js) || uri.endsWith(index.jsp)) { chain.doFilter(req, resp); return; } // 未登录跳回登录页 if (request.getSession().getAttribute(loginUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }过滤器的拦截规则用/*表示所有请求先进这一关。chain.doFilter是链式调用的关键当前过滤器完成自己的逻辑后把请求交给下一个过滤器或目标Servlet这一行不调用请求就断在这里。这个模式在java面试题里叫FilterChain在Spring里叫拦截器原理是同一套。判断放行名单时如果源码里有/admin开头的后台路径注意一定要单独要求管理员身份而不是只登录就行。4.3 BaseDao把JDBC重复代码收拢三种通用方法订餐系统里菜品查询、用户查询、订单写入都要用JDBC每个DAO都写一遍Connection、PreparedStatement、ResultSet代码会膨胀到没法维护。常见做法是先写一个BaseDao把重复逻辑收拢public class BaseDao { // 通用增删改传入SQL和可变参数返回受影响行数 public int update(String sql, Object... params) { try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { for (int i 0; i params.length; i) { ps.setObject(i 1, params[i]); } return ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(数据库更新失败, e); } } // 通用查询配合RowMapper把结果集转成对象列表 public T ListT query(String sql, RowMapperT mapper, Object... params) { ListT list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { for (int i 0; i params.length; i) { ps.setObject(i 1, params[i]); } try (ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(mapper.map(rs)); } } } catch (SQLException e) { throw new RuntimeException(数据库查询失败, e); } return list; } // 结果集映射接口 public interface RowMapperT { T map(ResultSet rs) throws SQLException; } }这里有几个参数细节值得说。Object... params是可变参数传入顺序必须和SQL里?占位符的顺序一一对应这是新手最容易踩的坑多传一个少传一个都会在setObject时下标越界。try-with-resources语法保证Connection、PreparedStatement、ResultSet在方法结束时自动关闭不会把数据库连接泄漏光。RowMapper是一个函数接口真正的DAO实现里会写rs.getInt(id)、rs.getString(name)这样的映射逻辑。如果源码用的还是手写连接的旧写法也别急着嫌弃那恰好说明教学性强。理解了BaseDao的封装思路以后换成mysql的数据库连接池或MyBatis思想是同一套。4.4 购物车到底存Session还是存数据库两种选型的取舍加购物车是订餐系统的核心交互设计上有经典分歧购物车数据放Session还是放数据库表。这套简单源码通常用Session方案因为实现成本最低// 从Session取购物车Map的key是菜品idvalue是数量 HttpSession session req.getSession(); MapInteger, Integer cart (MapInteger, Integer) session.getAttribute(cart); if (cart null) { cart new HashMapInteger, Integer(); session.setAttribute(cart, cart); } int foodId Integer.parseInt(req.getParameter(foodId)); cart.put(foodId, cart.getOrDefault(foodId, 0) 1); // 加购完成跳回菜品列表 resp.sendRedirect(req.getContextPath() /food/list);Session方案的优点是开发快、不用建表、不用单独做购物车记录的增删改查缺点是用户清Cookie或换浏览器购物车就没了Session里对象存多了还占服务器内存。如果源码表结构里有cart表那就是数据库方案登录后把购物车记录写入数据库用户随时回来数据都还在。两种方案在答辩时都能讲出道理关键是说清楚“为什么这样选”。简单的订餐系统用Session够用下单即清空购物车不是长期数据做生产级商城才考虑落库或者两者结合未登录先用Session登录后合并进数据库表。提示Session方案里购物车Map的key是Integer类型的菜品id从request取参数时用Integer.parseInt转一次不转会报NumberFormatException。这类类型转换异常是老项目里出现频率极高的运行时错误。4.5 下单事务订单主表和明细表必须一起成功下单是整个系统里唯一必须保证数据一致性的操作。如果订单头写进了orders表明细写order_item时失败用户会看到一个没有内容的订单这就是脏数据。解决办法是手动控制事务Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交后续操作合并成一个事务 // 1. 插入订单主表拿回自增主键 PreparedStatement ps1 conn.prepareStatement( INSERT INTO orders(user_id, total_price, create_time, status) VALUES(?,?,?,?), Statement.RETURN_GENERATED_KEYS); ps1.setInt(1, userId); ps1.setBigDecimal(2, totalPrice); ps1.setTimestamp(3, new Timestamp(System.currentTimeMillis())); ps1.setInt(4, 1); // 1表示新订单待处理 ps1.executeUpdate(); int orderId 0; ResultSet rs ps1.getGeneratedKeys(); if (rs.next()) orderId rs.getInt(1); // 2. 循环写入订单明细用批量执行减少网络往返 PreparedStatement ps2 conn.prepareStatement( INSERT INTO order_item(order_id, food_id, food_name, price, count) VALUES(?,?,?,?,?)); for (CartItem item : cartItems) { ps2.setInt(1, orderId); ps2.setInt(2, item.getFoodId()); ps2.setString(3, item.getFoodName()); ps2.setBigDecimal(4, item.getPrice()); ps2.setInt(5, item.getCount()); ps2.addBatch(); } ps2.executeBatch(); conn.commit(); // 全部成功才提交两张表一起生效 } catch (SQLException e) { if (conn ! null) conn.rollback(); // 任何一步失败全部回滚 throw new RuntimeException(下单失败订单已回滚, e); } finally { if (conn ! null) conn.close(); }这个写法有三个关键点。setAutoCommit(false)是事务开关打开后JDBC不再每执行一条SQL就自动提交而是等最后的commit统一提交。getGeneratedKeys用于拿自增主键订单明细必须引用这个新生成的orderId。addBatch和executeBatch是批量操作把多条INSERT合并成一次数据库往返数据量大时性能差距明显。rollback要包在catch里任何一条SQL失败前面已经执行的所有写入全部撤销。提示事务必须在同一个Connection上执行中途不能换连接。这也是为什么下单方法要自己拿连接而不是调用4.3节BaseDao里每次新建连接的便捷方法。理解了下单事务java面试题里“怎么保证数据一致性”这类问题就有了实例答案。这套源码里可能只有下单用到事务但把这一处讲透比背十遍框架事务注解都管用。5. 常见的翻车现场环境、编码、JDBC与部署避坑排查清单5.1 中文全变问号请求和响应两个方向都要设UTF-8现象页面标题、用户注册的昵称、订单里的菜品名全部显示成???查看数据库里存的也是问号。原因中文乱码有三个独立环节任何一个掉了都会翻车。一是HTTP请求参数没解码Tomcat默认按ISO-8859-1处理POST参数二是Servlet响应没指定编码浏览器按默认编码解析三是JDBC连接串没带characterEncoding参数驱动写入数据库时按错误编码转换。解决按链路上三个点逐一修。Servlet里在读取任何参数之前先执行req.setCharacterEncoding(UTF-8)响应开头写resp.setContentType(text/html;charsetUTF-8)JSP页面顶部保持% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8%数据库连接串加useUnicodetruecharacterEncodingutf8。三个点全对上中文才能走完全程。用Navicat看数据时如果还是乱码把连接编码也切成UTF-8这是查看端的问题不是程序问题。5.2 JDBC报Communications link failure先把SSL和时区两个参数加上现象启动项目后访问菜品列表页面报java.sql.SQLException: Communications link failure下面跟着SSL连接握手失败或者The server time zone value...无法识别。原因MySQL 8.0默认开启SSL和严格时区校验而老源码里的驱动和连接串都停留在MySQL 5.7时代。驱动版本老了不认新协议连接串又没告诉服务器用哪个时区握手就断在半路。解决连接串改成jdbc:mysql://localhost:3306/ordering?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai。useSSLfalse关掉SSL握手serverTimezoneAsia/Shanghai把时区对齐到东八区。如果驱动是mysql-connector-java 8.0.x还会要求allowPublicKeyRetrievaltrue才能处理新的密码认证一并加上。改完记得重启Tomcat只刷新页面没用连接参数是在类加载时读进去的。5.3 mysql命令报Error 2002 (HY000)服务没起来和socket路径是两回事现象在终端敲mysql -u root -p报ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。原因这行报错的本质是MySQL服务进程没启动或者客户端在找socket文件而服务端只监听TCP端口。Windows上常见于MySQL服务没注册成系统服务、手动启动后又被关掉Linux上常见于安装后没执行service mysql start。解决先确认服务状态。Windows打开服务管理器找MySQL项点启动Linux执行sudo service mysql start或systemctl start mysqld。如果服务在但还报错连接时显式走TCPmysql -h 127.0.0.1 -P 3306 -u root -p。这个坑跟Java代码没有关系是纯粹的数据库服务没就位。排查时先绕开Java直接用命令行连一次MySQL能连上再回头看代码一步就定位到问题在谁身上。5.4 Tomcat 10跑老项目javax.servlet全家报ClassNotFoundException现象项目部署后访问任何页面直接500控制台一堆ClassNotFoundException类名全是javax.servlet.***。原因Tomcat 10把Servlet规范命名空间从javax.servlet迁移到了jakarta.servlet。老项目里所有import javax.servlet.http.HttpServlet的代码在Tomcat 10上找不到对应类。这是容器的破坏性升级跟源码本身没有关系。解决换回Tomcat 8.5或9.x老代码一行不用改。如果机器上只有Tomcat 10不要去全局替换源码里的import工程量大且容易漏直接在IDEA的Run配置里把Tomcat home指向8.5版本就行。这个现象解释了前面反复强调版本配平的原因很多“源码跑不起来”的案例最后都定位在容器版本上。5.5 传统JSP项目打包war部署后404上下文路径和访问路径对不上现象本地IDEA跑得好好的打成war丢到Tomcat的webapps目录启动后访问 http://localhost:8080/xxx/ 却404。原因war部署后Tomcat以war文件名作为上下文路径。如果war包叫ordering.war访问根是/ordering如果代码里页面跳转写死了/index.jsp没有带上下文前缀浏览器请求的就是根应用下的路径自然404。另一种常见情况是war包解压失败WEB-INF/lib下的驱动jar没打进去页面报的错就成了NoClassDefFoundError。解决部署后先确认应用上下文访问http://localhost:8080/war包名/ 试试。源码里所有Servlet重定向和JSP里的链接规范做法是通过${pageContext.request.contextPath}动态拼上下文路径不是写死/index.jsp。传统JSP项目打包war本身不难IDEA里Build - Build Artifacts选war包但路径问题才是部署后真正的坎。顺手提醒一句往Linux服务器部署时注意webapps目录的写权限和端口放行老项目在这些小地方花的排错时间往往比写代码还多。6. 把订餐源码改造成毕业设计扩展点、验证技巧与答辩准备6.1 三个低成本高回报的扩展点第一个是菜品搜索和分类筛选。加一个/search的ServletSQL里用LIKE拼name字段再在导航栏加一个搜索框工作量半天功能却是评委最爱演示的入口。第二个是订单状态流转。在orders表status字段上做文章后台把“已下单”改成“已接单”再改成“已送达”前端订单列表按状态显示不同文案涉及一次UPDATE和一次页面判断但讲起来能覆盖状态机概念。第三个是简单的统计报表。后台首页加一行SQLSELECT DATE(create_time) AS d, COUNT(*) AS orderCount FROM orders GROUP BY DATE(create_time) ORDER BY d DESC;把结果渲染成表格就是“数据可视化”的小亮点。这三个扩展都不需要动表结构的大手术代码量小但对答辩评分的作用很明显而且每一个都能对应到一个java面试题或数据库考点。6.2 用一张功能测试清单验证闭环改造完别直接交按业务顺序跑一遍回归测试确认原始功能没被改坏用例操作预期结果注册访问register.jsp填写信息跳转登录页user表新增记录登录正确账号密码登录进入首页导航栏显示用户名加购菜品页点“加入购物车”购物车数量加一下单购物车提交订单orders和order_item同时插入新记录后台处理管理员登录后台更新订单状态前台订单状态同步变化未登录拦截退出后直接访问购物车URL被过滤器重定向到登录页拿到这套源码后我习惯先跑一遍这个清单再动手改代码而不是上来就拆。原因很简单只有先确认原始版本全绿后面报错才知道责任在自己改的代码上而不是怀疑原包有问题。每改完一个扩展点再把相关用例重新跑一遍比最后统一排查省时得多。6.3 交付前多花十分钟导出war和写README最后一步是把项目导出成war包在干净的Tomcat上部署一遍。这一步能暴露一堆IDE里看不到的问题缺驱动包、路径写死、JSP编译错误。同时写一个README把环境版本、数据库初始化步骤、默认管理员账号密码写清楚。我自己养成的习惯是凡是经手过的源码包一定在电脑上留一份“能跑版本说明文档”的压缩包防止三个月后答辩前突然打不开再来回找资料。这套javajspmysql的订餐系统跑通它不难但把它讲透、改好、交付得干净才是它真正值钱的地方。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网