ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

校园外卖管理系统JavaWeb实战:Servlet+JSP+MyBatis从建库到答辩避坑

2026/9/23 5:00:18 拓冰建站 浏览量
校园外卖管理系统JavaWeb实战:Servlet+JSP+MyBatis从建库到答辩避坑 简介这是一套面向高校计算机相关专业学生的JavaWeb校园外卖管理系统完整项目源码适合作为课程设计、期末大作业或毕业设计的参考方案也便于初学者通过实操理解Web项目开发流程。压缩包共329个文件约28.7MB其中Java源文件73个构成后端业务逻辑XML配置119个支撑框架与项目配置另有HTML、CSS、JavaScript文件搭建前端页面PNG、JPG等图片资源用于界面展示并附带SQL数据库脚本整体结构清晰、模块划分明确。项目为纯手打高分作品代码完整、下载即可运行小白也能按说明完成部署与调试。目前已有395人学习下载读者可从中获得一套可直接运行的校园外卖系统实现涵盖订单管理、用户管理、菜品展示等核心模块便于快速理解JavaWeb开发中的分层设计与前后端交互思路也可在此基础上进行二次开发或功能扩展。1. 校园外卖管理系统到底在做什么从一份 95 分大作业说起很多同学做课程设计时第一反应是去搜「javaweb项目完整案例mysql」结果翻到的不是电商就是图书管理业务逻辑跟校园场景对不上。校园外卖管理系统这个题目之所以在课程设计里反复出现是因为它天然覆盖了 JavaWeb 的完整技术链路用户登录鉴权、商家菜品管理、订单状态流转、数据库增删改查一个都不少。它要解决的核心问题很具体——把「学生下单、商家接单、骑手配送、后台管理」这条线用一套 Web 系统串起来而不是做一个玩具级的 CRUD 页面。这套系统适合两类人一是正在找 javaweb项目完整案例、需要交大作业或课程设计的在校生二是想用 MyBatis 源码、mysql 数据库连接池这些点练手的中级学习者。它不涉及分布式和高并发但把 Servlet、JSP、JDBC、事务、连接池这些基础打扎实比盲目追微服务更有价值。下面我按实际能跑通的路径把选型、建库、核心代码和踩坑点讲清楚。2. 技术选型与工程结构为什么这套组合能稳过答辩2.1 为什么是 Servlet JSP MyBatis 而不是 Spring Boot课程设计场景下选型的第一原则是「老师能看懂、自己能讲清」。Spring Boot 虽然开发快但自动配置把很多细节藏起来了答辩时被问「请求怎么到 Controller 的」容易卡壳。Servlet JSP MyBatis 这套组合请求链路是显式的浏览器发请求 → web.xml 或注解映射到 Servlet → Servlet 调 Service → Service 调 Mapper → MyBatis 执行 SQL → 返回 JSP 渲染。每一层都能在代码里指出来这是大作业拿高分的关键。具体版本上我一般用 JDK 8 或 11、Tomcat 8.5/9、MySQL 5.7/8.0、MyBatis 3.5.x。IDEA 里创建 javaweb 项目时选 Java Enterprise勾选 Web Application注意别选成 Maven 的 Spring Initializr那是另一条路。工程目录按标准分层src/ main/ java/ com.campus.controller/ // Servlet 层 com.campus.service/ // 业务逻辑 com.campus.dao/ // Mapper 接口 com.campus.entity/ // 实体类 com.campus.util/ // 工具类DBUtil、MD5 等 resources/ mybatis-config.xml // MyBatis 全局配置 mapper/ // XML 映射文件 webapp/ WEB-INF/web.xml jsp/ // 页面 static/ // css/js/图片这个结构的好处是职责清晰答辩时打开目录就能讲分层。注意 entity 类的字段名要和数据库列名对应或者用 resultMap 显式映射别依赖驼峰自动转换的玄学MyBatis 默认不开这个。2.2 数据库连接池怎么配才不翻车热词里「mysql的数据库连接池」出现频率很高因为这是新手最容易出问题的地方。直接用 DriverManager 每次新建连接几十个并发就崩。常见做法是用 Druid 或 HikariCP课程设计里 Druid 更常见因为自带监控页面答辩演示时是个加分项。在druid.properties里配置driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/campus_takeout?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse usernameroot password你的密码 initialSize5 maxActive20 maxWait3000参数说明initialSize是启动时创建的连接数maxActive是最大连接数课程设计场景 20 足够maxWait是获取连接的超时毫秒数设 3000 避免请求卡死。serverTimezone必须写否则 MySQL 8 会报时区错误这是血泪经验。加载时用静态代码块读配置文件保证全局只初始化一次public class DBUtil { private static DataSource dataSource; static { try { Properties props new Properties(); props.load(DBUtil.class.getClassLoader() .getResourceAsStream(druid.properties)); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new RuntimeException(连接池初始化失败, e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }逻辑说明静态块在类加载时执行一次DruidDataSourceFactory根据 properties 创建数据源。之后所有 DAO 通过DBUtil.getConnection()拿连接用完必须 closeDruid 会归还到池里而不是真正关闭。忘记 close 是连接池耗尽的第一大原因建议用 try-with-resources。3. 从建库到跑通第一个接口可抄作业的完整步骤3.1 建表脚本与字段设计数据库设计直接决定后面代码好不好写。校园外卖的核心表有用户表、商家表、菜品表、订单表、订单明细表。订单表用状态字段驱动流转这是整个系统的骨架。CREATE DATABASE campus_takeout DEFAULT CHARSET utf8mb4; USE campus_takeout; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role TINYINT NOT NULL DEFAULT 0 COMMENT 0学生 1商家 2管理员, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_dish ( id INT PRIMARY KEY AUTO_INCREMENT, merchant_id INT NOT NULL, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, INDEX idx_merchant (merchant_id) ); CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, merchant_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待接单 1已接单 2配送中 3已完成 4已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user (user_id), INDEX idx_status (status) );字段说明role用 TINYINT 而不是字符串省空间且查询快order_no单独生成而不是用自增 id 暴露给前端避免被猜到订单量status用数字编码前端展示时映射成文字。索引加在 merchant_id、user_id、status 上因为列表查询基本都按这几个字段过滤。注意 DECIMAL 用于金额别用 FLOAT浮点误差在对账时是灾难。3.2 用 MyBatis 写第一个查询接口以「查询某商家上架菜品列表」为例Mapper 接口和 XML 分开写。接口public interface DishMapper { ListDish selectByMerchant(Param(merchantId) int merchantId); }XML 映射mapper namespacecom.campus.dao.DishMapper select idselectByMerchant resultTypecom.campus.entity.Dish SELECT id, merchant_id AS merchantId, name, price, stock, status FROM t_dish WHERE merchant_id #{merchantId} AND status 1 ORDER BY id DESC /select /mapper逻辑说明#{merchantId}是预编译占位符能防 SQL 注入别用${}拼接。resultType指向实体类列名用 AS 转成驼峰避免开mapUnderscoreToCamelCase的配置依赖。Service 层调用public ListDish listDishes(int merchantId) { SqlSession session MyBatisUtil.getSession(); try { return session.getMapper(DishMapper.class).selectByMerchant(merchantId); } finally { session.close(); } }参数说明MyBatisUtil.getSession()内部用sqlSessionFactory.openSession()注意默认不自动提交增删改要session.commit()。查询不需要 commit但 session 必须 close否则连接泄漏。如果项目里用 Spring 管理事务这层可以交给容器但纯 Servlet 项目手动管理更透明。3.3 订单状态流转的事务处理下单是最容易出 bug 的地方扣库存、生成订单、写订单明细三步必须在一个事务里。任何一步失败都要回滚否则会出现「库存扣了但订单没生成」的脏数据。public String createOrder(int userId, int merchantId, ListOrderItem items) { SqlSession session MyBatisUtil.getSession(); try { DishMapper dishMapper session.getMapper(DishMapper.class); OrderMapper orderMapper session.getMapper(OrderMapper.class); BigDecimal total BigDecimal.ZERO; for (OrderItem item : items) { int affected dishMapper.reduceStock( item.getDishId(), item.getNum()); if (affected 0) { session.rollback(); return 库存不足 item.getDishId(); } total total.add(item.getPrice() .multiply(new BigDecimal(item.getNum()))); } Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setMerchantId(merchantId); order.setTotalAmount(total); orderMapper.insert(order); session.commit(); return success; } catch (Exception e) { session.rollback(); throw new RuntimeException(下单失败, e); } finally { session.close(); } }逻辑说明reduceStock的 SQL 要写成UPDATE t_dish SET stock stock - #{num} WHERE id #{id} AND stock #{num}用affected 0判断库存不足这比先查再改更安全避免并发超卖。整个流程手动 commit/rollback保证原子性。generateOrderNo可以用时间戳加随机数别用 UUID 太长影响索引。4. 避坑与排查那些让答辩当场翻车的问题4.1 中文乱码现象是页面显示问号原因是编码链路没统一现象JSP 页面提交的中文参数到后台变成乱码数据库里存进去也是问号。原因通常有三处没统一JSP 页面pageEncoding、请求体编码、数据库连接 URL 编码。解决JSP 顶部写% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %在 Servlet 里request.setCharacterEncoding(UTF-8)且必须在getParameter之前调用数据库 URL 加characterEncodingutf8建库用utf8mb4。三处都对了才不会乱。4.2 连接池耗尽现象是请求卡住然后报超时原因是连接没归还现象系统跑一会儿后所有请求都卡住日志报wait millis 3000, active 20。原因基本是 DAO 里拿了 Connection 或 SqlSession 没 close尤其是异常路径下。解决所有资源用 try-with-resources 或 finally 块关闭Druid 监控页面看 active 数是否持续增长把maxWait调小让它快速失败而不是无限等。这个坑在答辩演示时如果触发基本就凉了。4.3 MyBatis 参数映射失败现象是报Parameter xxx not found现象Mapper 方法有多个参数时XML 里直接写#{name}报找不到参数。原因MyBatis 对多参数默认命名是 arg0、param1不认你的变量名。解决接口方法每个参数加Param(name)注解或者传一个实体/DTO 对象。单参数且是对象时可以直接用属性名但多参数必须加注解这是最常见的低级错误。4.4 订单重复提交现象是用户连点两次生成两笔订单现象网络慢时用户连点提交按钮数据库出现两条相同订单。原因前端没防重后端没幂等。解决前端提交后禁用按钮后端在生成订单前用order_no唯一索引兜底或者用 Redis 做短时锁课程设计里用数据库唯一约束就够。order_no加 UNIQUE 约束后重复插入会抛异常捕获后返回「请勿重复提交」。4.5 Tomcat 启动报 404现象是项目部署了但访问不到现象IDEA 里 Tomcat 启动成功浏览器访问localhost:8080/是 404。原因通常是 context path 没配对或者 web.xml 里 servlet 映射写错。解决在 IDEA Run Configuration 的 Deployment 里看 Application context 是什么访问时带上检查WebServlet(/dish)或 web.xml 的 url-pattern 是否和请求路径一致确认 JSP 放在 webapp 下而不是 WEB-INF 里直接访问WEB-INF 下的资源不能直接 URL 访问必须走 Servlet 转发。5. 进阶技巧让这套系统在答辩里多拿 10 分5.1 用拦截器做统一登录校验每个 Servlet 都写一遍登录判断太啰嗦用 Filter 统一处理。核心逻辑是放行登录、注册、静态资源其余请求检查 session 里有没有 user。WebFilter(/*) public class AuthFilter implements Filter { private static final SetString WHITE_LIST Set.of( /login, /register, /static, /css, /js); public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI() .substring(request.getContextPath().length()); boolean white WHITE_LIST.stream().anyMatch(uri::startsWith); if (white || request.getSession().getAttribute(user) ! null) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() /login); } } }逻辑说明WHITE_LIST用Set.of定义免登录路径startsWith匹配前缀。session 里有 user 就放行否则重定向到登录页。这个 Filter 让代码干净很多答辩时讲「统一鉴权」比逐个 Servlet 讲更有条理。注意WebFilter需要 web.xml 的metadata-completefalse或版本 3.0 才生效。5.2 订单状态机的校验表订单状态不能随便改必须有合法流转规则。用一张表约束Service 层查表判断当前状态允许操作目标状态操作角色0 待接单接单1 已接单商家0 待接单取消4 已取消学生1 已接单开始配送2 配送中商家2 配送中完成3 已完成学生1 已接单拒单4 已取消商家实现时把这张表写成MapInteger, SetInteger或枚举更新前校验from - to是否合法非法直接返回错误。这样能防止「已完成订单被改回待接单」这类脏操作也是答辩时体现业务思考的加分点。5.3 一个我踩过的坑别在 JSP 里写业务逻辑刚开始做的时候图省事在 JSP 里直接% %查数据库、算金额结果页面又慢又难维护改一个字段要翻好几个文件。后来全部改成 Servlet 查好数据放 request 域JSP 只负责用 EL 和 JSTL 渲染。这个习惯一旦养成代码可读性提升明显答辩老师翻代码时也不会皱眉头。我的习惯是JSP 里出现超过三行 Java 代码就说明该抽到 Servlet 或 Service 了。希望帮到你。本文还有配套的精品资源点击获取