免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Servlet+JDBC点餐系统实战:MVC架构、事务处理与部署避坑指南

Servlet+JDBC点餐系统实战:MVC架构、事务处理与部署避坑指南 简介基于MVC开发模式的Java Web点餐系统完整项目包面向正在完成毕业设计、课程设计或希望进阶Servlet/JDBC实战的开发者。项目采用ServletJDBC实现经典三层协作覆盖用户注册登录、菜品选择、下单等完整业务链路能够帮助快速理解MVC模式中模型、视图与控制器的职责划分以及HTTP请求处理、数据库持久化等核心机制。压缩包共139个文件大小约3.76MB包含20个JSP页面、6个Java源文件、81张运行截图、数据库SQL脚本和需求文档还提供了依赖jar包便于直接导入使用或二次开发。已有61人学习下载适合作为课程设计参考、毕业设计原型或练手项目。通过研读源码可以系统掌握Servlet生命周期、JDBC连接与操作、HttpSession会话跟踪、用户认证与权限控制等关键技能并借助附带文档梳理从环境部署、功能测试到项目发布的全流程。1. 原生 ServletJDBC 点餐系统老技术栈凭什么还在毕设里霸榜原生 Servlet JDBC 做的点餐系统被大量毕业设计选中是有原因的不依赖 Spring 框架把请求分发、数据库操作、会话管理这些底层链路全部暴露在代码里老师一眼能看出学生的真实功底。这套基于 MVC 架构实现的点餐系统核心是 CenterController 统一入口配上 UserService、FoodService、FoodTypeService 和 DBUtil登录、菜品浏览、下单整条业务闭环都是手写 Servlet 和 JDBC 完成没有一行框架代码。它适合三类人被毕设或课程设计卡住、需要一份能跑通全流程源码的在校生想搞清楚 Servlet 生命周期和 JDBC 事务配合的初学者以及需要一份教学参考的带课老师。资源包里附带了需求文档能对应上每个模块的设计动机答辩准备时特别有用。2. MVC 分层在点餐系统里的真实落地CenterController、Service、DBUtil 各司其职2.1 为什么点餐系统要用前端控制器而不是一页配一个 Servlet很多第一次做 Java Web 项目的人最容易写出来的结构是登录一个 LoginServlet注册一个 RegisterServlet查菜品一个 FoodListServlet下单一个 OrderServlet。功能少的时候没问题一旦加到十几个页面web.xml 里全是 Servlet 映射改一个路径就要动配置文件类和 URL 的对应关系散落各处后面维护的人想找一个功能入口得翻半天。这个项目在 controller 层只保留了一个 CenterController所有请求先进它再靠 action 参数分配给后面的 Service。这种写法在 MVC 里叫前端控制器Front Controller模式Spring MVC 的 DispatcherServlet 干的也是同一件事。好处有三条第一编码设置、登录校验、权限判断只需要写一份放在入口处统一做不会漏第二URL 和 Servlet 类不再是硬绑定的一对一关系新增业务只需要加方法分支不用到处改映射第三业务逻辑全部下沉到 ServiceController 变薄接手的人看代码时不容易迷路。从压缩包反推这个项目的请求路径设计浏览器访问的一定是项目名/CenterController?action...这样的格式action 取值可能是login、register、listFood、createOrder等CenterController 拿到 action 字符串后做判断再调用对应的 Service 方法最后用 forward 或 redirect 把响应交给 JSP 页面。整个过程里JSP 只负责显示Service 只负责业务DBUtil 只负责拿连接职责边界很干净。对比一页一个 Servlet的写法这种结构在答辩时也很好讲你可以指着 CenterController 说所有请求从这里进老师顺着链路往下看就能走通全流程。2.2 从 class 文件反推这个包的类职责和调用链打开压缩包看到的一堆 .class 文件其实把架构泄露得很彻底先看这张对应表类名推断职责在 MVC 中的角色CenterController接收所有 HTTP 请求按 action 分发控制器CUserService登录、注册、用户信息校验模型M业务层FoodService菜品增删改查、上下架模型M业务层FoodTypeService菜品分类管理模型M业务层DCService点餐主服务下单、订单查询、购物车处理模型M业务层DBUtil数据库连接获取与关闭模型M基础工具这里有一个容易看错的地方DCService 名字缩写得比较随意它其实是 DianCanService点餐服务在点餐系统里才是核心——用户把菜品加入购物车、提交订单、查看历史订单这些动作最终都会落到 DCService 上。而 UserService 和 FoodService 更像是被它调用的基础服务这也符合常见商业项目里订单服务依赖用户服务和菜品服务的结构。调用链大概是这样的浏览器发起请求到 TomcatTomcat 根据 web.xml 的映射找到 CenterControllerCenterController 解析 action 参数调用 UserService/FoodService/DCService 的某个方法Service 内部通过 DBUtil 拿 Connection执行 SQL结果封装成 Java 对象setAttribute 放进 requestforward 到 JSP 渲染。这个链路里的每一步都能在代码里找到一个明确的类或方法这正是 MVC 模式适合教学的核心原因它把请求怎么走变成了一个肉眼可见的流程。2.3 仿照这个项目手写一个最简分发入口如果你打算抄这个思路自己起一个项目Controller 层不需要任何框架一个 Servlet 就能扛住。下面是最小可用的分发骨架// CenterController.java —— 前端控制器入口 WebServlet(/CenterController) public class CenterController extends HttpServlet { protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 统一编码POST 请求的中文参数全靠这一行保底 request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); String action request.getParameter(action); String uri null; if (login.equals(action)) { UserService service new UserService(); boolean ok service.login( request.getParameter(username), request.getParameter(password)); if (ok) { // 登录成功写 Session跳主页 request.getSession().setAttribute(user, request.getParameter(username)); uri index.jsp; } else { // 登录失败回登录页带错误提示 request.setAttribute(msg, 用户名或密码错误); uri login.jsp; } } else if (listFood.equals(action)) { FoodService service new FoodService(); request.setAttribute(foodList, service.queryAll()); uri foodList.jsp; } // 所有分支统一在这里 forward避免每个分支里重复写转发逻辑 if (uri ! null) { request.getRequestDispatcher(uri).forward(request, response); } } }这段代码里有两个点值得留意。第一是重写service方法而不是doGet/doPost因为点餐系统的请求大部分是表单 POST但偶尔也有链接跳转的 GETservice方法能同时接管两种请求不用写两个重载代价是你需要自己确认请求方法是否影响了业务逻辑。第二是统一 forward 出口所有分支只负责算uri最后只写一次转发代码能明显减少重复代码。补充一个参数约定WebServlet注解是 Servlet 3.0 之后提供的如果你的 Tomcat 是 8 以上直接用注解就行不用去 web.xml 里写servlet和servlet-mapping两段配置。如果老师要求必须用 web.xml那就去掉注解在 web.xml 里加一段映射效果完全一样。不少人的项目明明代码没问题就是注解和 web.xml 同时配置导致 Servlet 被加载了两次单例变多例这是我自己见过的事故之一。3. Servlet 请求链路与 JDBC 事务把登录、点餐、下单走成一条完整闭环3.1 Servlet 生命周期和 doGet/doPost 的分工理解Servlet 的生命周期说起来是三个方法init、service、destroy但真正干活时你只需要记住一个结论Servlet 是单实例多线程的一个 CenterController 实例会被所有请求共享所以成员变量里绝对不能放用户相关的数据。常见的翻车写法是在 Servlet 里加一个成员变量String username请求 A 刚把它设成自己的名字请求 B 进来就把值覆盖了。在点餐系统这种多用户同时下单的场景里这会直接导致订单串号——用血泪经验说这是看起来能用、一上并发就崩的典型代表。doGet 和 doPost 的分工其实没那么玄浏览器地址栏输入网址、点击超链接发的是 GET表单设置了methodpost才发 POST。GET 的参数拼接在 URL 上会被浏览器历史、服务端访问日志记录下来所以登录这种带密码的请求必须用 POST。这个项目里 CenterController 重写了 service 方法统一处理从架构上回避了这个问题但如果拿到源码的是你自己写的分支还是要注意涉及密码、下单、修改数据的请求一律走 POST查询、跳转可以走 GET。一条请求从浏览器出发到达 Service 层中间要经过的节点是Tomcat 解析 HTTP 报文创建HttpServletRequest和HttpServletResponse对象根据 web.xml 或注解找到 Servlet 类调用service方法读取参数执行业务把结果写回 response。排查问题时建议盯住这条链路上的参数是否取到、业务是否执行、结果是否返回三个节点用 debug 断点打在每个节点边界上很快就能定位是前端没传参、还是后端逻辑出错。比在代码里到处加 print 然后肉眼扫日志靠谱得多。3.2 登录模块参数处理、会话跟踪、密码校验登录是点餐系统的第一个硬功能它在 UserService 里对应一个类似login的方法。最常见的实现方式是把用户名密码从请求里拿出来到数据库的 t_user 表里查一遍匹配上了就登录成功。下面是这个模块的核心代码骨架// UserService.java —— 用户登录校验 public User login(String username, String password) { Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); // 用 ? 占位符杜绝字符串拼接 SQL String sql SELECT id, username, nickname FROM t_user WHERE username ? AND password ?; ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); rs ps.executeQuery(); if (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); u.setNickname(rs.getString(nickname)); return u; } return null; // 查不到 → 用户名或密码错误 } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(登录查询失败, e); } finally { DBUtil.close(rs, ps, conn); } }写这段时有几个习惯直接决定项目质量。第一finally里关闭资源的顺序必须和打开顺序相反先关 ResultSet再关 PreparedStatement最后关 Connection漏掉任何一环Tomcat 跑几天就会因为连接被占满而假死报Too many connections这是压测时最常见的坑。第二这里查出来的 User 对象不带 password 字段因为密码只用来比对不应该再传回页面否则前端能看到数据库里的敏感字段。第三SQL 全部用?占位符而不是字符串拼接原因在第四章单说但记住一条铁律任何用户输入进 SQL一律走 PreparedStatement。登录成功之后CenterController 会调用request.getSession().setAttribute(user, u)把用户对象放进 Session。HTTP 是无状态的服务器不认识第二次请求是谁Session 就是服务器发给浏览器的临时通行证Tomcat 通过一个 JSESSIONID 的 Cookie 来维系。点餐系统里加入购物车、下单都要先判断 Session 里有没有 user没有就跳回登录页。这个判断逻辑放在 CenterController 的入口处统一做最合适不用每个 Service 都查一遍。3.3 点餐下单JDBC 事务里先插订单头再插明细点餐系统里业务价值最重的方法在 DCService 里大概对应一个createOrder用户确认购物车后要生成一条订单记录同时把购物车里的每道菜拆成订单明细。这里有一个必须用事务的理由订单主表和订单明细表是主从关系如果先插入订单头成功、再插明细时数据库报错就会出现一笔没有明细的幽灵订单反过来明细比订单头多也会对不上账。只有把两条插入放在同一个事务里要么都成功要么都回滚数据才不会烂。// DCService.java —— 下单事务示例 public boolean createOrder(int userId, ListCartItem cartItems) { Connection conn null; PreparedStatement psOrder null; PreparedStatement psItem null; ResultSet rs null; try { conn DBUtil.getConnection(); // 关键一步关闭自动提交事务边界从这里开始 conn.setAutoCommit(false); // 1. 插入订单主表拿回自增主键 String sqlOrder INSERT INTO t_order(user_id, total_price, status) VALUES(?, ?, 0); psOrder conn.prepareStatement(sqlOrder, PreparedStatement.RETURN_GENERATED_KEYS); double total 0; for (CartItem item : cartItems) { total item.getPrice() * item.getCount(); } psOrder.setInt(1, userId); psOrder.setDouble(2, total); psOrder.executeUpdate(); rs psOrder.getGeneratedKeys(); int orderId 0; if (rs.next()) { orderId rs.getInt(1); } // 2. 插入订单明细每道菜一行 String sqlItem INSERT INTO t_order_item(order_id, food_id, count, price) VALUES(?, ?, ?, ?); psItem conn.prepareStatement(sqlItem); for (CartItem item : cartItems) { psItem.setInt(1, orderId); psItem.setInt(2, item.getFoodId()); psItem.setInt(3, item.getCount()); psItem.setDouble(4, item.getPrice()); psItem.addBatch(); // 批量执行减少网络往返 } psItem.executeBatch(); // 3. 全部成功才提交 conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); // 出错就把数据回滚到事务开始前的状态 try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { DBUtil.close(rs, psItem, psOrder, conn); } }这段代码有三个参数层面的细节要说明。第一prepareStatement(sqlOrder, PreparedStatement.RETURN_GENERATED_KEYS)里的第二个参数是为了拿到数据库自增主键生成订单主表的 id 后明细表才能拿它当外键这一步很容易被忽略不传这个参数时getGeneratedKeys()会返回空结果集。第二addBatch()配合executeBatch()是为了一次性把多条明细提交给数据库比一条条 executeUpdate 快很多点餐系统明细量不大但这是个值得养成的好习惯。第三setAutoCommit(false)和最后的commit/rollback必须成对出现很多人在 catch 里忘了回滚一旦中途抛异常未提交的数据会一直占着数据库资源后面的事务全部卡住。还有一点坑比较隐蔽如果执行过程中conn本身是 null调用conn.rollback()会抛空指针所以 rollback 前要判空。上面代码里已经处理了但很多新手自己写的时候会漏掉异常没排掉反而冒出一个新异常。判断事务是否生效最简单的方法是故意在明细插入前抛一个异常跑完再看订单表里有没有残留数据没有就说明事务是靠谱的。4. 数据库设计与 DBUtil 封装表结构、连接参数、预处理语句的边界4.1 点餐系统常见的四张核心表及其关系一个点餐系统的数据模型通常绕不开四类实体用户、菜品类型、菜品、订单。对应到数据库里就是 t_user、t_food_type、t_food、t_order 四张表再加上 t_order_item 保管订单明细。它们的关系是t_user 和 t_order 是一对多t_food_type 和 t_food 是一对多t_order 和 t_order_item 是一对多t_food 和 t_order_item 是多对一——菜品被多条订单记录引用但每条明细只对应一道菜。表名关键字段说明t_userid, username, password, nickname用户账号username 建议加唯一索引t_food_typeid, type_name, sort菜品分类sort 控制前台显示顺序t_foodid, food_name, type_id, price, imagetype_id 关联分类表t_orderid, user_id, total_price, status, create_timestatus 用 0/1/2 表示待付款/已付款/已取消t_order_itemid, order_id, food_id, count, priceprice 存下单时的单价快照有一个设计细节值得模仿t_order_item 里单独存一份price而不是下单时再去关联 t_food 表查价格。原因很简单菜品价格是会被调整的如果订单明细的价格字段跟着菜品表走历史订单的金额就会被篡改财务对不上账。下单时把钱数快照进明细表是一个很成熟的商业设计思路在毕业设计里写出来也是加分项。另外status 字段建议用数字枚举而不是直接存中文。存数字页面端用 switch 映射成待付款/已付款/已取消好处是数据库体积小、查询可以走索引、程序里不容易出现已付款和已 付 款这种肉眼难辨的空格差异。很多初版代码图省事直接存字符串后面做统计时一个个 LIKE 匹配只能回过头来改。如果这是课程设计老师看到字段设计里出现这种细节印象分会高不少。4.2 DBUtil 的写法从驱动注册到资源关闭的完整封装DBUtil 在这个项目里承担的角色是数据库连接的统一出口。原始写法是每个 Service 里都写一段Class.forName加DriverManager.getConnection十处调用就有十份重复代码。封装之后所有连接从 DBUtil 拿所有资源通过 DBUtil 关出问题时只需要改一个类。以下是标准的封装骨架// DBUtil.java —— 数据库连接工具类 public class DBUtil { // 注意 URL 里的 useUnicode 和 characterEncoding 两个参数 private static final String URL jdbc:mysql://localhost:3306/order_food ?useUnicodetruecharacterEncodingUTF-8 useSSLfalseserverTimezoneAsia/Shanghai; private static final String USERNAME root; private static final String PASSWORD root; static { try { // 加载驱动只会执行一次放在静态块里 Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } public static void close(ResultSet rs, Statement stmt, Connection conn) { // 资源关闭顺序必须相反rs → stmt → conn if (rs ! null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }这里要重点说三个参数。第一useSSLfalse是为了关掉 MySQL 8 默认开启的 SSL 握手不关的话连接时会报 SSL 告警虽然不影响功能但日志里一大片红色警告很容易让答辩时手忙脚乱。第二serverTimezoneAsia/Shanghai是 MySQL 8 强制要求的时区参数不设置会直接报The server time zone value异常连接根本建立不起来。第三驱动类名有两种MySQL 5.x 用com.mysql.jdbc.DriverMySQL 8.x 用com.mysql.cj.jdbc.Driver写错会抛 ClassNotFoundException这个坑在避坑章里再展开。再说一个取舍这个 DBUtil 是每次调用都新开一个连接属于最简单的连接管理方式。没问题撑住课程设计的并发量绰绰有余。如果以后要上生产再换 Druid 或 HikariCP 连接池也不难把getConnection方法内部换成从连接池拿就行调用方代码一行都不用改这就是封装的价值。想用 VSCode 写这类工程也可以装 Java Extension Pack 和 Tomcat 插件但这份工程是 Eclipse 的直接导入最省事。4.3 PreparedStatement 防注入的原理和常见误用JDBC 里执行 SQL 有 Statement 和 PreparedStatement 两种方式点餐系统里的登录、下单全部应该用后者。原因不光是代码整洁而是安全层面的硬要求。看下面两种写法// 危险写法字符串拼接SQL 注入一打一个准 String sql SELECT * FROM t_user WHERE username username AND password password ; // 安全写法参数化查询值和 SQL 结构分离 String sql SELECT * FROM t_user WHERE username ? AND password ?;危险写法的问题在于如果用户在用户名框里输入 OR 11拼出来的 SQL 就变成了WHERE username OR 11 AND password 恒真条件直达数据库等于没有密码也能登录。PreparedStatement 的原理是先把带?的 SQL 发到数据库端做预编译再通过 setString 等方法单独传值值永远被当作数据而不是 SQL 代码段处理无论里面写什么危险符号都不会改变已经编译好的 SQL 结构。有一个常见误用需要注意很多人写代码图省事先拼一个完整 SQL 再prepareStatement(fullSql)这等于把预处理白白丢掉了和直接用 Statement 一样危险。PreparedStatement 的唯一正确用法就是 SQL 里写?所有动态值全部走 setXxx 传入。另外字符串类型的 setString 会自动处理引号转义不需要手动替换单引号手动替换反而容易把用户真实输入改坏。你可以在自己项目里用一个简单的 jdbc 增删改查页面做个对比实验把 OR 11放进拼接版登录框再看参数版会不会被绕过。5. 部署避坑与常见问题Eclipse导入、Tomcat启动、中文乱码、驱动冲突先说一个总判断这类原生 Servlet 项目的部署坑九成集中在编译期和运行期环境不一致。源码是 Eclipse 工程结构压缩包里带着 .classpath 和 org.eclipse.wst.common.component但你本机 IDE 版本、Tomcat 版本、MySQL 版本、JDK 版本只要有一个对不上就会冒出一个奇怪的问题。下面五条是我把这些项目从导入到跑通全流程时最常踩的坑。5.1 导入期项目不认 Web Facet、驱动类找不到坑一导入后项目在 Eclipse 里是普通 Java 工程部署按钮消失现象Import → Existing Projects into Workspace 之后Package Explorer 里的项目图标没有那个小地球标记右键 Run As 里没有 Run on Server部署到 Tomcat 的选项是灰的。原因压缩包里的 .project 和 .classpath 是原开发者的 Eclipse 版本生成的缺了 Web 工程相关的 nature 声明或者 .settings 目录没打包进来导致 Eclipse 无法识别它的 Dynamic Web Module 特性。解决右键项目 → Properties → Project Facets勾选 Dynamic Web Module 和 Java版本选 3.0 以上配合 Tomcat 8勾完点 Apply再去 Properties → Targeted Runtimes 里把 Tomcat 勾上。两步做完项目才具备可部署的身份。如果 Facets 面板不可点说明项目还没被识别成 Java 工程先右键 → Configure → Convert to Faceted Form 转一下。坑二改完代码启动报 ClassNotFoundException: com.mysql.cj.jdbc.Driver现象Tomcat 启动或者点击登录按钮时控制台出现ClassNotFoundException: com.mysql.cj.jdbc.Driver页面 500。原因MySQL 驱动 jar 只加了 Build Path没有放进WEB-INF/lib。Build Path 是编译期对 Eclipse 可见Tomcat 运行期是独立的 classloader只从WEB-INF/lib和WEB-INF/classes加载类。另外驱动版本和类名不匹配也会报同样的错代码里写的是 MySQL 8 的类名但 jar 是 5.x 的。解决确认mysql-connector-java-x.x.x.jar在WebContent/WEB-INF/lib下如果手头有多个版本的 jar全删干净只留一个避免 classpath 里两个驱动加载顺序出问题。类名对照MySQL 5.x 用com.mysql.jdbc.Driver8.x 用com.mysql.cj.jdbc.Driver看 jar 文件名里的版本号来判断别靠猜。5.2 运行期中文乱码三连、8080 被占、登录秒登回坑三中文参数和页面输出两处乱码改一处没用现象登录张三显示成乱码或者登录成功但 JSP 页面上菜品名称是菱形问号。更隐蔽的是改了 request 编码后登录不乱码了但列表页中文还是乱。原因完整链路是浏览器页面编码 → Tomcat 请求解码 → JDBC 连接 MySQL 编码 → 数据库表字符集 → JSP 输出编码任何一环不一致都乱。常见排查盲区是忘了 GET 请求的编码在 server.xml 里配以及忘了数据库表本身使用 utf8mb4。解决一套查下来JSP 第一行% page pageEncodingUTF-8 contentTypetext/html;charsetUTF-8 %CenterController 或过滤器里request.setCharacterEncoding(UTF-8)且必须在第一次 getParameter 之前执行JDBC URL 带characterEncodingUTF-8建表 SQL 末尾加DEFAULT CHARSETutf8mb4Tomcat 的 server.xml 里 Connector 加URIEncodingUTF-8。五个点全部一致才算收工少一个都算没解决。坑四Tomcat 起不来报 8080 端口被占用现象Eclipse 启动 Tomcat 报Port 8080 required by Tomcat v9.0 Server is already in use或者访问页面 Connection refused。原因之前跑的 Tomcat 进程没有完全关闭或者有孤儿进程还占着端口Windows 上尤其容易出现 Eclipse 崩了、java.exe 进程却残留的情况。解决Windows 命令行执行netstat -ano | findstr 8080拿到占用端口的 PID再taskkill /F /PID pidmacOS/Linux 用lsof -i:8080看进程号再kill -9。嫌麻烦就直接改 Tomcat 的conf/server.xml里 Connector 的 port 为 8081改完访问路径跟着变不影响项目本身。另外看一下 Eclipse 的 Servers 视图确认之前的 Tomcat 实例处于 Stopped 状态再重新启动往往能解决一半问题。坑五密码验证对但页面永远被弹回登录页现象账号密码都输对了后端也不报错但每次登录成功都被弹回 login.jsp看起来像永远登录不进去。原因最常见是 Session 和 request 用混了request.setAttribute(user, u)放在 login 分支里它跟着当前请求走一旦后续 sendRedirect 到新请求数据就丢另一种是密码比较用了两个内容相同的 String 用比较结果是 false导致逻辑走到了失败分支。解决强制用request.getSession().setAttribute(user, u)存登录态String 内容比较全部用equals别用。同时检查跳转方式有没有混用 forward 和 sendRedirect——需要带数据过页面的地方统一用 forward需要换 URL 且不需要 request 数据的地方才用 sendRedirect。分不清的时候记住一个口诀forward 是代转交数据还在sendRedirect 是你重新请求一切重来。6. 从交作业到能答辩三个验证技巧和一处值得扩展的代码位6.1 功能验收清单拿到这套源码不是跑起来就算完事建议按下面这张表逐项验收每一项都对应答辩时可能被老师追问的问题验收项操作预期结果背后考察点登录链路正确/错误密码各试一次成功进主页失败回登录页带提示Session 与重定向中文数据添加一道麻婆豆腐列表页正常显示三层编码是否统一下单事务购物车加 3 道菜提交订单表和明细表同时出现记录JDBC 事务边界非法参数URL 直接访问订单页链接被拦截跳登录页权限校验重启恢复重启 Tomcat 后查历史订单数据仍在JDBC 持久化6.2 一个值得照抄的初始化技巧ServletContextListener答辩时老师常会问你的项目里有没有全局初始化的代码。一个非常实用的写法是用监听器在 Tomcat 启动时就完成一些准备工作比如加载热门菜品缓存到内存比每个请求都查数据库快很多// AppListener.java —— 应用启动初始化 WebListener public class AppListener implements ServletContextListener { public void contextInitialized(ServletContextEvent sce) { // Tomcat 启动后执行一次初始化热门菜品列表 FoodService foodService new FoodService(); ServletContext app sce.getServletContext(); app.setAttribute(hotFoods, foodService.queryHotFoods(10)); } }这个技巧在点餐系统里的用武之地是把菜品分类、推荐菜品这类不常变的数据在启动时加载到 application 作用域JSP 里直接用${hotFoods}展示不用每次都查数据库。答辩时讲出启动预加载 缓存这一句能明显拉开和纯 CRUD 作业的差距。说回我自己带学生的经验每年改这种 Servlet 项目我最先看的就是资源关闭和事务回滚两处很多人代码写成作业能跑、一发就炸毛病全出在这。所以从那以后我每次给项目收尾都会强制走一遍杀进程、清缓存、重启 Tomcat、按验收表跑功能这四步缺一步都不敢说这项目能交。这套流程你也可以直接用希望帮到你。本文还有配套的精品资源点击获取
返回列表