ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

纯Servlet+JSP+JDBC超市订单系统实战解析

纯Servlet+JSP+JDBC超市订单系统实战解析 简介本资源是一套完整可用的JavaWeb课程设计项目——超市订单管理系统面向计算机专业本科生及Java初学者解决课程设计选题难、开发周期长、数据库对接不熟等实际问题。系统采用JSPServletMySQL技术栈涵盖用户管理、商品浏览、订单生成与查询等核心业务模块代码结构清晰、注释规范适合作为课设参考或Web开发入门实践案例。压缩包共135个文件包含24个Java后端逻辑类、24个JSP页面、25个JavaScript交互脚本、20个PNG图标资源、9个CSS样式文件及3个SQL建表与初始化脚本整体仅2.69MB轻量易部署。目前已有281人学习下载资源附带完整数据库脚本与LayUI前端框架支持含layer.css、laydate.css等预览文件开箱即用无需额外配置即可本地运行并调试全流程业务逻辑。1. 这不是“又一个JavaWeb课设”95分超市订单系统背后的真实工程逻辑你下载了一个叫“基于Javaweb的超市订单管理系统源码数据库95分以上课设项目.zip”的压缩包解压后看到一堆jsp、servlet、web.xml、MySQL脚本和IDEA工程文件——但真正决定它能不能跑通、能不能改、能不能当真实项目底子的根本不是“有没有登录页”而是三层结构是否物理隔离、DAO层是否可替换、事务边界是否清晰、SQL注入防护是否落地到每一处request.getParameter()调用。这不是教学演示是高校课设里少有的、接近企业级MVC雏形的实战切片用户下单时库存扣减与订单生成必须原子性管理员导出报表要防OOM商品搜索支持模糊分类双条件而所有这些在95分项目里都靠手写JDBC事务控制 手动SQL预编译 JSP中EL表达式严格过滤实现。它适合两类人一是大三学生正卡在“为什么我的servlet总404”“为什么数据库连不上”“为什么增删改查总丢数据”的临界点二是刚转Java后端的新人想用一个无Spring、无MyBatis、纯ServletJSPJDBC的干净项目反向吃透Web容器生命周期、HTTP请求流转、连接池原理和SQL执行链路。别急着跑起来——先看清它为什么能得95分不是界面花哨是每个功能模块都埋了可验证的工程约束。2. 从解压到启动Idea运行Javaweb项目配置的硬核四步法这个项目不依赖Maven自动拉包也不用Tomcat Manager部署它的生命力藏在手动配置的路径映射、显式声明的依赖顺序、以及web.xml里精确到servlet-class的加载时机中。直接Import Project进IDEA90%会卡在“找不到javax.servlet.ServletException”。下面这四步是我带过17届学生踩出来的最小可行路径跳过任意一步都会触发玄学报错。2.1 正确识别项目类型拒绝自动识别强制指定为Java Web提示IDEA默认按pom.xml或build.gradle识别项目类型。这个项目没有pom.xml必须手动干预。解压zip后在IDEA中选择File → Open → 选中解压后的根目录不是.zip文件弹窗出现时取消勾选“Auto-import”点击OK右键项目根目录 →Add Framework Support… → 勾选“Java EE: Web Application” → 点击OK此时IDEA会在项目根目录下生成/out/artifacts/和/web/WEB-INF/web.xml结构这才是正确起点如果跳过第3步IDEA会把src当普通Java Module处理javax.servlet.*包永远标红——因为没告诉IDEA“这是一个Web应用”它不会自动关联Servlet API。2.2 Tomcat配置版本锁死在8.5.x且必须指定CATALINA_BASE这个项目在web.xml中声明了web-app version3.0对应Servlet 3.0规范。Tomcat 9默认启用Servlet 4.0会导致jsp-config标签解析失败Tomcat 7又缺少WebServlet注解支持虽然本项目没用但部分工具类会调用。实测兼容性最好的是Tomcat 8.5.932023年LTS版。配置时注意两个致命参数# 在Tomcat配置的VM Options中必须添加 -Dcatalina.base/path/to/your/tomcat85 \ -Dcatalina.home/path/to/your/tomcat85 \ -Dfile.encodingUTF-8参数说明-Dcatalina.base指向你解压后的Tomcat目录如/opt/apache-tomcat-8.5.93不是IDEA内置的Tomcat。项目里的context.xml和server.xml修改只对独立Tomcat生效-Dcatalina.home必须与base相同否则Tomcat启动时读不到lib/servlet-api.jar缺少-Dfile.encodingUTF-8会导致JSP中文乱码且错误日志显示为?排查难度翻倍。2.3 依赖库手动挂载lib目录里的jar才是唯一真相项目/WebContent/WEB-INF/lib/下有6个jarmysql-connector-java-5.1.47.jar注意不是8.x驱动类名是com.mysql.jdbc.Driverjstl-1.2.jarstandard-1.1.2.jarJSP标签库缺一不可commons-dbutils-1.7.jar轻量级JDBC工具项目用它封装QueryRunnercommons-beanutils-1.9.4.jar配合DbUtils做ResultSet→JavaBean转换commons-logging-1.2.jar日志门面实际输出到控制台操作步骤在IDEA中右键/WebContent/WEB-INF/lib/→Add as Library…命名建议填javaweb-lib-95scoreScope选Compile关键动作打开Project Structure → Modules → Dependencies确认这6个jar的Order and Export中全部勾选且排在Tomcat 8.5.93之前为什么顺序重要如果tomcat-servlet-api.jar在mysql-connector-java.jar前面ClassLoader会优先加载Tomcat自带的旧版JDBC驱动导致Class.forName(com.mysql.jdbc.Driver)抛ClassNotFoundException——而错误堆栈只会显示“Driver not found”根本看不出是类加载冲突。2.4 部署路径校准war包名必须与web.xml中 一致项目web.xml第3行写着display-nameSuperMarketOrderSystem/display-name而IDEA默认打包的war名是untitled.war。启动时Tomcat会尝试访问http://localhost:8080/untitled/login.jsp但实际资源在/SuperMarketOrderSystem/下必然404。修正方法Project Structure → Artifacts → 选中war条目 → Output Directory右侧点击...将路径改为/path/to/your/project/out/artifacts/SuperMarketOrderSystem_war_exploded在Artifact name框中输入SuperMarketOrderSystem.war重启Tomcat不是Reload是Stop → Start验证是否成功启动后访问http://localhost:8080/SuperMarketOrderSystem/应看到登录页HTML源码中包含title超市订单管理系统/title。若仍404请检查Tomcat日志中是否有Deploying web application directory [/path/to/tomcat/webapps/SuperMarketOrderSystem]字样——没有则说明部署根本没触发。3. 数据库落地MySQL 5.7兼容性、字符集陷阱与初始化脚本执行链项目附带的supermarket.sql脚本看似简单但直接source supermarket.sql会触发3类静默失败表创建成功但字段乱码、外键约束被忽略、自增ID从0开始导致后续插入报错。这不是MySQL配置问题而是脚本本身没声明字符集上下文。3.1 创建数据库时必须显式指定CHARSET和COLLATE-- ✅ 正确写法复制粘贴到MySQL命令行 CREATE DATABASE supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;为什么不用utf8MySQL的utf8实际是utf8mb3最多存3字节字符不支持emoji、部分生僻汉字而utf8mb4才是真正的UTF-8。项目中商品名称字段product_name VARCHAR(100)可能含“¥”“®”符号用utf8会导致插入时截断或报错Incorrect string value。为什么用utf8mb4_unicode_ci而不是utf8mb4_general_ci后者是MySQL 5.5前的旧排序规则对中文排序不准确如“张”和“章”可能排错序unicode_ci基于Unicode 4.0标准中文姓名、商品名排序更可靠。3.2 初始化脚本执行前必须关闭foreign_key_checkssupermarket.sql中包含ALTER TABLE order_detail ADD CONSTRAINT fk_order_id FOREIGN KEY (order_id) REFERENCES orders(id);这类语句。如果MySQL全局变量foreign_key_checksON默认值而建表顺序是先建order_detail再建orders就会报错ERROR 1215 (HY000): Cannot add foreign key constraint。解决方案-- 在执行supermarket.sql前先运行 SET FOREIGN_KEY_CHECKS 0; SOURCE /path/to/supermarket.sql; SET FOREIGN_KEY_CHECKS 1;血泪经验曾有学生反复执行脚本失败最后发现是Navicat图形界面执行时自动加了SET FOREIGN_KEY_CHECKS1;前缀导致SET FOREIGN_KEY_CHECKS 0;被覆盖。务必用MySQL原生命令行mysql -u root -p执行不要用GUI工具。3.3 用户权限配置不能只给ALL PRIVILEGES要精确到表级项目代码中数据库连接URL为String url jdbc:mysql://localhost:3306/supermarket?useSSLfalseserverTimezoneAsia/Shanghai;这意味着Java程序用的是root用户绝对不行。生产环境课设也需模拟最小权限原则-- 创建专用用户 CREATE USER sm_userlocalhost IDENTIFIED BY Sm2024Pass; -- 授予仅必要的权限 GRANT SELECT, INSERT, UPDATE, DELETE ON supermarket.orders TO sm_userlocalhost; GRANT SELECT, INSERT, UPDATE, DELETE ON supermarket.order_detail TO sm_userlocalhost; GRANT SELECT, INSERT, UPDATE ON supermarket.products TO sm_userlocalhost; GRANT SELECT ON supermarket.categories TO sm_userlocalhost; -- 刷新权限 FLUSH PRIVILEGES;修改代码中的连接凭证打开src/com/utils/DBUtil.java找到第12行private static final String URL jdbc:mysql://localhost:3306/supermarket?useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; // ← 改成 sm_user private static final String PASSWORD 123456; // ← 改成 Sm2024Pass不改这里程序启动时抛Access denied for user rootlocalhost但错误日志会掩盖真实原因——因为DBUtil.getConnection()的catch块只打印数据库连接失败不输出SQLException.getMessage()。4. 核心业务逻辑拆解订单生成的事务控制、库存扣减的并发安全、JSP页面的数据绑定这个项目95分的关键在于它用最原始的JDBC实现了企业级订单系统的三个核心契约下单成功库存已扣减订单已创建支付状态待确认且三者不可分割。它没用Spring的Transactional全靠Connection.setAutoCommit(false)手动控制。我们来逆向工程这段代码。4.1 订单生成的原子性保障Connection级别的事务边界查看src/com/service/OrderService.java的createOrder()方法public boolean createOrder(Order order, ListOrderDetail details) { Connection conn null; try { conn DBUtil.getConnection(); // 获取连接 conn.setAutoCommit(false); // 关键关闭自动提交 // 步骤1插入orders主表 String sql1 INSERT INTO orders (user_id, total_amount, status, create_time) VALUES (?, ?, ?, ?); PreparedStatement ps1 conn.prepareStatement(sql1, Statement.RETURN_GENERATED_KEYS); ps1.setInt(1, order.getUserId()); ps1.setDouble(2, order.getTotalAmount()); ps1.setString(3, unpaid); ps1.setTimestamp(4, new Timestamp(System.currentTimeMillis())); ps1.executeUpdate(); // 步骤2获取刚插入的order_id主键回填 ResultSet rs ps1.getGeneratedKeys(); int orderId 0; if (rs.next()) { orderId rs.getInt(1); } // 步骤3批量插入order_detail明细 String sql2 INSERT INTO order_detail (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?); PreparedStatement ps2 conn.prepareStatement(sql2); for (OrderDetail detail : details) { ps2.setInt(1, orderId); ps2.setInt(2, detail.getProductId()); ps2.setInt(3, detail.getQuantity()); ps2.setDouble(4, detail.getPrice()); ps2.addBatch(); } ps2.executeBatch(); // 步骤4扣减库存关键必须在同一个conn中执行 for (OrderDetail detail : details) { String sql3 UPDATE products SET stock stock - ? WHERE id ? AND stock ?; PreparedStatement ps3 conn.prepareStatement(sql3); ps3.setInt(1, detail.getQuantity()); ps3.setInt(2, detail.getProductId()); ps3.setInt(3, detail.getQuantity()); // 防超卖stock quantity int affected ps3.executeUpdate(); if (affected 0) { throw new RuntimeException(库存不足订单创建失败); } } conn.commit(); // 全部成功才提交 return true; } catch (Exception e) { if (conn ! null) { try { conn.rollback(); // 任一环节失败就回滚 } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { DBUtil.close(conn); // 必须释放连接 } }逻辑说明conn.setAutoCommit(false)是事务起点所有后续操作都在同一事务上下文中ps1.getGeneratedKeys()获取自增主键避免SELECT LAST_INSERT_ID()的并发风险库存扣减SQL中AND stock ?是防超卖的最后防线比应用层判断更可靠因为数据库行锁会阻塞并发更新DBUtil.close(conn)在finally块中执行确保连接归还连接池否则10次下单后连接池耗尽后续请求全部卡死。4.2 JSP页面的数据绑定EL表达式安全过滤与空值防御项目所有JSP页面如/admin/product_list.jsp都用${product.name}输出数据但没做任何XSS过滤。直接渲染用户输入的商品名会引发漏洞。修复方案在/WEB-INF/web.xml中添加JSTL的taglib声明项目已存在确认如下taglib taglib-urihttp://java.sun.com/jsp/jstl/core/taglib-uri taglib-location/WEB-INF/lib/jstl-1.2.jar/taglib-location /taglib在JSP顶部引入c标签库% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfn urihttp://java.sun.com/jsp/jstl/functions %将所有${xxx}替换为c:out value${xxx} escapeXmltrue/!-- 原写法危险 -- td${product.name}/td !-- 安全写法 -- tdc:out value${product.name} escapeXmltrue//tdescapeXmltrue会将scriptalert(1)/script转义为lt;scriptgt;alert(1)lt;/scriptgt;浏览器只显示文本不执行JS。这是JSP时代最有效的XSS防御手段比后端过滤更前置。4.3 分页查询的性能陷阱LIMIT OFFSET的深分页优化管理员页面/admin/order_list.jsp用SELECT * FROM orders LIMIT ?, ?实现分页但当页码1000时MySQL要扫描前10000行才能返回第10001行响应时间从20ms飙升到2s。项目没做优化但你可以加一层修改src/com/dao/OrderDao.java的findOrdersByPage()方法// 原SQL深分页慢 String sql SELECT * FROM orders ORDER BY create_time DESC LIMIT ?, ?; // 优化后SQL用游标分页适用于后台管理 String sql SELECT * FROM orders WHERE id ? ORDER BY id DESC LIMIT ?; // 调用时传入上一页最大id如第1页取id1000则第2页where id1000为什么有效LIMIT 10000,10需要MySQL定位到第10000行再取10行而WHERE id ?利用主键索引直接定位复杂度O(logN)。课设虽不要求但这是你向面试官展示“我懂真实场景”的关键细节。5. 避坑指南95分项目里藏着的5个血泪故障点这个项目拿95分不代表没坑。恰恰相反高分项目往往在边缘场景埋得更深。以下是我在指导32个学生复现时高频触发的5个故障每一条都附带现象、根因和可立即执行的修复命令。5.1 现象登录成功后跳转到空白页浏览器地址栏显示/SuperMarketOrderSystem/success.jsp但页面无内容原因success.jsp中jsp:include page/common/header.jsp/路径错误。项目实际路径是/WebContent/common/header.jsp但jsp:include的page属性是相对于当前JSP的路径而success.jsp在根目录/common/header.jsp会去找/common/header.jsp不存在不是/WebContent/common/header.jsp。解决将success.jsp第5行改为jsp:include pagecommon/header.jsp/ !-- 去掉开头的/ --5.2 现象添加商品时提示“数据库连接失败”但MySQL服务正常、账号密码正确原因DBUtil.java中Class.forName(com.mysql.jdbc.Driver)在MySQL 5.1.47驱动中有效但如果本地MySQL是8.0版本即使驱动用5.1.47JDBC URL必须加allowPublicKeyRetrievaltrueuseSSLfalse否则握手失败。解决修改DBUtil.java的URLprivate static final String URL jdbc:mysql://localhost:3306/supermarket?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue;5.3 现象订单列表中日期显示为2024-01-01 00:00:00.0而非2024-01-01 14:30:22原因orders.create_time字段类型是DATETIME但JDBC读取时未指定时区MySQL服务器时区与JVM时区不一致。项目代码用rs.getTimestamp(create_time)但没设置Calendar参数。解决在OrderDao.java的findOrdersByPage()方法中修改rs.getTimestamp()调用// 原代码 order.setCreateTime(rs.getTimestamp(create_time)); // 改为 Calendar cal Calendar.getInstance(TimeZone.getTimeZone(Asia/Shanghai)); order.setCreateTime(rs.getTimestamp(create_time, cal));5.4 现象管理员修改商品价格后前端显示仍是旧价格刷新页面才更新原因ProductServlet.java中doPost()处理修改逻辑后重定向用了response.sendRedirect(product_list.jsp)但没清除request.setAttribute(msg, 修改成功)导致下次请求时product_list.jsp仍读到旧的msg而商品列表数据没重新查询。解决在sendRedirect前确保request作用域已清空// 删除这行它让msg残留 // request.setAttribute(msg, 修改成功); // 改为用session传递一次性的提示消息 request.getSession().setAttribute(msg, 修改成功); response.sendRedirect(product_list.jsp); // product_list.jsp中用c:if test${not empty sessionScope.msg}显示然后c:remove varmsg scopesession/5.5 现象Linux服务器部署后上传商品图片失败日志显示java.io.FileNotFoundException: /upload/xxx.jpg (No such file or directory)原因项目代码中图片保存路径写死为D:/supermarket/upload/Windows路径Linux下不存在该目录且Java无权在根目录创建文件夹。解决在Linux上创建上传目录mkdir -p /home/tomcat/upload chmod 755 /home/tomcat/upload修改src/com/servlet/ProductServlet.java中路径// 原代码 String uploadPath D:/supermarket/upload/; // 改为适配Linux/Windows String uploadPath System.getProperty(user.home) /upload/; // 或更稳妥从web.xml读取初始化参数 // context-paramparam-nameupload.dir/param-nameparam-value/home/tomcat/upload/param-value/context-param6. 进阶技巧把课设项目变成你的技术简历锚点拿到这个95分项目别只满足于“跑起来”。真正让它成为你技术成长的支点得做三件事加监控、改架构、造测试。下面是我给学生布置的“毕业设计升级包”做完就能在简历上写“独立重构JavaWeb电商系统”。6.1 给JDBC加连接池监控用Druid替换原生DBUtil原项目用DriverManager.getConnection()每次新建连接100并发时MySQL连接数爆满。换成Druid后你能看到实时连接数、SQL执行时间、慢SQL告警。步骤下载druid-1.2.16.jar Maven Repository 放入/WEB-INF/lib/创建/WEB-INF/classes/druid.propertiesdriverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/supermarket?useSSLfalseserverTimezoneAsia/Shanghai usernamesm_user passwordSm2024Pass initialSize5 maxActive20 minIdle5 maxWait60000 timeBetweenEvictionRunsMillis60000 minEvictableIdleTimeMillis300000 validationQuerySELECT 1 testWhileIdletrue testOnBorrowfalse testOnReturnfalse poolPreparedStatementstrue maxPoolPreparedStatementPerConnectionSize20 filtersstat,wall,log4j修改DBUtil.java// 删除原getConnection()方法 // 新增静态块初始化Druid static { try { Properties props new Properties(); props.load(DBUtil.class.getClassLoader().getResourceAsStream(druid.properties)); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); // 复用连接池 }效果访问http://localhost:8080/SuperMarketOrderSystem/druid/index.htmlDruid内置监控页能看到SQL执行TOP10、实时活跃连接数、慢SQL列表。这是你向面试官证明“我懂生产级数据库治理”的铁证。6.2 把JSP页面转成前后端分离用Vue重写商品管理页保留后端Servlet不变只替换前端。这样你既练了Vue又理解了API契约设计。步骤在/WebContent/下新建vue-product/目录放index.html和app.jsapp.js用axios调用原Servlet// 原JSP的/product_list.jsp对应接口 axios.get(/SuperMarketOrderSystem/ProductServlet?actionlist) .then(res { this.products res.data; // 后端需改Servlet返回JSON });修改ProductServlet.java增加JSON响应分支if (list.equals(action)) { ListProduct list productService.findAll(); response.setContentType(application/json;charsetUTF-8); response.getWriter().write(new Gson().toJson(list)); // 需引入gson-2.10.1.jar }关键收获你亲手完成了“传统JavaWeb”到“RESTful API”的演进理解了Content-Type、跨域、JSON序列化等概念不再是只会抄JSP的学生。6.3 写JUnit单元测试覆盖核心Service层项目没测试但你可以补。用JUnit 4写3个关键测试// src/test/java/com/service/OrderServiceTest.java public class OrderServiceTest { private OrderService service; Before public void setUp() { service new OrderService(); // 清空测试数据 executeUpdate(DELETE FROM order_detail); executeUpdate(DELETE FROM orders); executeUpdate(UPDATE products SET stock 100 WHERE id 1); } Test public void testCreateOrderSuccess() { Order order new Order(); order.setUserId(1); order.setTotalAmount(99.9); ListOrderDetail details new ArrayList(); OrderDetail detail new OrderDetail(); detail.setProductId(1); detail.setQuantity(2); detail.setPrice(49.95); details.add(detail); boolean result service.createOrder(order, details); assertTrue(result); // 验证库存扣减 int stock queryForInt(SELECT stock FROM products WHERE id 1); assertEquals(98, stock); // 100 - 2 } }执行命令在IDEA中右键测试类 → Run OrderServiceTest。通过率100%才叫真正吃透业务逻辑。这是我筛选实习生时必问的问题“你给课设项目写了几个单元测试”我带过的最后一届学生把这套流程走完后有7个人拿到了一线大厂的实习offer。他们没吹“精通Spring Cloud”只说“我用原生Servlet写过带事务、防超卖、可监控的订单系统并把它升级成VueRESTful架构”。技术人的底气从来不是背了多少框架名词而是你亲手把一个95分课设拧成了自己能力的螺丝钉。希望帮到你。本文还有配套的精品资源点击获取
返回列表