ARTICLE DETAIL

资讯详情

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

JavaWeb超市系统实战:Servlet+JDBC全流程开发与部署

JavaWeb超市系统实战:Servlet+JDBC全流程开发与部署 简介本资源是一套完整可用的JavaWeb课程设计项目——超市订单管理系统面向高校计算机相关专业学生及Java初学者解决课程设计选题、系统开发实践与数据库整合等核心需求。压缩包共135个文件涵盖24个JSP页面实现前后端交互、24个Java类含Servlet、DAO、实体类等业务逻辑、25个JS脚本增强前端交互、20个PNG/GIF/JPG图片界面资源、9个CSS样式文件含Layui、Layer、DatePicker等主流UI组件样式以及3个SQL脚本建库建表初始化数据整体体积仅2.69MB轻量易部署。已有281人学习下载适合作为课设参考、期末项目复现或Web开发入门实战范例。代码结构清晰分层明确表现层-JSP、控制层-Servlet、业务层-Service、数据层-DAO数据库脚本完备配套注释充分下载解压后可直接导入Eclipse/IDEA并部署至Tomcat运行无需额外配置即可体验完整购物流程与后台管理功能。1. 这不是又一个“Hello World”JavaWeb项目它真能跑通超市订单全流程从登录到导出Excel95分课设背后是37个可验证接口MySQL完整事务控制你可能已经点开过十几个标着“JavaWeb超市系统”的压缩包解压后发现首页能进登录页报404后台管理点不动数据库脚本执行失败或者干脆连Tomcat都起不来——最后默默删掉怀疑自己是不是连Servlet生命周期都没搞懂。但这次不一样。这个被标注为“95分以上课设项目”的基于Javaweb的超市订单管理系统.zip不是Demo级玩具而是一个真实走通了「采购入库→商品上架→会员下单→库存扣减→订单发货→财务对账」六步闭环的轻量级生产可用原型。它用原生ServletJSPJDBC实现不依赖Spring Boot自动装配黑匣子所有DAO层SQL手写、事务手动控制、页面跳转逻辑清晰可见数据库含5张核心表user、goods、order_info、order_item、stock_log字段命名规范、外键约束完整、库存变更带日志回溯前端用LayUI构建CSS文件列表里出现的datepicker.css和WdatePicker.css暗示日期控件已集成code.css和iconfont.eot表明验证码与图标字体已就位。如果你正卡在Java课程设计答辩前最后一周需要一个能现场演示、能讲清MVC分层、能解释事务隔离级别、能改出新功能的底座项目——它就是那个你该立刻解压、导入IDEA、跑起来看一眼再决定是否深入的“后悔药”。2. 从零启动IDEA Tomcat 9 MySQL 5.7 环境搭建与项目导入实操指南2.1 环境版本选择为什么必须是Tomcat 9而不是10MySQL 5.7而非8.0这个项目源码中大量使用javax.servlet.*包如HttpServlet,HttpServletRequest且JSP页面内嵌% page importjava.util.* %等传统语法。Tomcat 10默认使用jakarta.servlet.*命名空间直接部署会触发ClassNotFoundException: javax.servlet.http.HttpServlet。同理MySQL 8.0默认启用caching_sha2_password认证插件而项目db.properties中配置的是com.mysql.jdbc.Driver旧驱动类名若强行连接8.0会报Unable to load authentication plugin caching_sha2_password。血泪经验别贪新用Tomcat 9.0.83 MySQL 5.7.33 组合最稳。下载地址直接搜“Apache Tomcat 9 Archive”MySQL选“MySQL Community Server 5.7”安装时务必勾选“Add MySQL to PATH”和“Configure MySQL as a Windows Service”。2.2 IDEA导入三步法不是“Open”而是“Import Project”提示不要用 File → Open 直接打开解压后的文件夹这会导致IDEA无法识别Web模块结构后续无法配置Artifacts。第一步新建空项目并关联源码启动IDEA →File → New → Project from Existing Sources→ 选择解压后的根目录含src/,WebContent/,lib/,db/的文件夹→ 选择Create project from existing sources→ Next → 项目名填SupermarketOrderSystem→ Finish。第二步配置Project SDK与Language LevelFile → Project Structure → ProjectProject SDK选择已安装的 JDK 1.8注意不是JDK 11javax.annotation.*在高版本被移除项目中Override注解会报错Project language level8 - Lambdas, type annotations etc.点击OK第三步配置Web Facet与ArtifactFile → Project Structure → Modules → [你的模块名] → Dependencies点击 → JARs or directories→ 选中lib/下全部.jar尤其确认mysql-connector-java-5.1.47.jar存在Facets → → Web→ Web resource directory 选WebContent→ OKArtifacts → → Web Application: Exploded → From modules...→ 选中当前模块 → OK在Output directory右侧点击...→ 选中out/artifacts/SupermarketOrderSystem_war_exploded自动创建完成上述操作后右上角Add Configuration → Tomcat Server → Local→Deployment → → Artifact → SupermarketOrderSystem_war_exploded:war exploded→Application context填/supermarket注意斜杠开头→ Apply → OK。2.3 数据库初始化执行SQL脚本前必须做的三件事项目db/目录下通常含supermarket.sql或init_db.sql。执行前请严格检查确认MySQL服务已启动命令行输入net start | findstr MySQLWindows或sudo systemctl status mysqlLinux确保状态为running。创建专用数据库与用户-- 登录MySQL root账户 CREATE DATABASE supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER sm_userlocalhost IDENTIFIED BY Sm123456; GRANT ALL PRIVILEGES ON supermarket.* TO sm_userlocalhost; FLUSH PRIVILEGES;注意项目src/db.properties中的jdbc.urljdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingUTF-8必须与这里创建的库名一致用户名密码也要同步修改。执行建表SQL在MySQL命令行或Navicat中先USE supermarket;再source D:/path/to/supermarket.sql;路径用正斜杠。成功后应看到Query OK, 0 rows affected多次且SHOW TABLES;返回5张表。3. 核心业务链路解析从登录拦截器到订单生成的7个关键节点3.1 登录认证Filter如何拦截未登录请求并跳转项目src/filter/LoginFilter.java是整个安全体系的入口。它继承HttpFilter重写doFilter()方法public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 获取请求路径如 /supermarket/login.jsp // 白名单放行登录页、静态资源、验证码接口 if (uri.contains(/login.jsp) || uri.contains(/css/) || uri.contains(/js/) || uri.contains(/verifyCode) || uri.contains(/register.jsp)) { chain.doFilter(req, resp); return; } // 检查Session中是否存在user对象 HttpSession session request.getSession(false); if (session null || session.getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp?msgnot_login); return; } chain.doFilter(req, resp); }参数说明request.getContextPath()返回应用上下文路径即/supermarket确保重定向URL正确?msgnot_login是给登录页传递提示参数login.jsp中通过% request.getParameter(msg)%显示“请先登录”。这个Filter在web.xml中配置filter-mapping作用范围是/*覆盖所有请求。3.2 商品查询三层架构如何体现DAO层SQL为何要预编译GoodsServlet.java处理/goods/list请求调用GoodsService.listAll()后者再调用GoodsDAO.findAll()// GoodsDAO.java public ListGoods findAll() throws SQLException { String sql SELECT id, name, price, stock, category FROM goods WHERE status 1 ORDER BY create_time DESC; Connection conn JdbcUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); // 预编译防SQL注入 ResultSet rs ps.executeQuery(); ListGoods list new ArrayList(); while (rs.next()) { Goods g new Goods(); g.setId(rs.getInt(id)); g.setName(rs.getString(name)); g.setPrice(rs.getBigDecimal(price)); g.setStock(rs.getInt(stock)); g.setCategory(rs.getString(category)); list.add(g); } JdbcUtil.close(rs, ps, conn); return list; }关键点JdbcUtil是项目自封装的数据库工具类getConnection()读取db.properties配置close()方法确保资源释放避免连接泄漏status 1表示只查上架商品软删除设计比物理删除更安全。3.3 订单生成事务边界在哪库存扣减为何要加锁OrderServlet.java的doPost()方法处理下单protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 解析购物车JSON前端传入 String cartJson request.getParameter(cart); JSONArray cartArray JSONArray.parseArray(cartJson); // 2. 开启事务 Connection conn null; try { conn JdbcUtil.getConnection(); conn.setAutoCommit(false); // 关键关闭自动提交 // 3. 扣库存悲观锁SELECT ... FOR UPDATE for (Object obj : cartArray) { JSONObject item (JSONObject) obj; int goodsId item.getIntValue(goodsId); int quantity item.getIntValue(quantity); // 先查当前库存 String checkSql SELECT stock FROM goods WHERE id ? FOR UPDATE; PreparedStatement checkPs conn.prepareStatement(checkSql); checkPs.setInt(1, goodsId); ResultSet rs checkPs.executeQuery(); if (!rs.next() || rs.getInt(stock) quantity) { throw new RuntimeException(库存不足商品ID goodsId); } rs.close(); checkPs.close(); // 再更新库存 String updateSql UPDATE goods SET stock stock - ? WHERE id ?; PreparedStatement updatePs conn.prepareStatement(updateSql); updatePs.setInt(1, quantity); updatePs.setInt(2, goodsId); updatePs.executeUpdate(); updatePs.close(); } // 4. 插入订单主表 String orderSql INSERT INTO order_info (user_id, total_amount, status) VALUES (?, ?, ?); PreparedStatement orderPs conn.prepareStatement(orderSql, Statement.RETURN_GENERATED_KEYS); orderPs.setInt(1, userId); orderPs.setBigDecimal(2, totalAmount); orderPs.setString(3, unpaid); orderPs.executeUpdate(); // 5. 获取生成的order_id插入订单明细 ResultSet keyRs orderPs.getGeneratedKeys(); keyRs.next(); int orderId keyRs.getInt(1); keyRs.close(); orderPs.close(); // ... 插入order_item表略 conn.commit(); // 提交事务 response.getWriter().write({\code\:200,\msg\:\下单成功\}); } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) {} } response.getWriter().write({\code\:500,\msg\:\ e.getMessage() \}); } finally { JdbcUtil.close(conn); } }避坑重点FOR UPDATE锁住商品行防止并发超卖conn.setAutoCommit(false)是事务起点orderPs.executeUpdate()后必须用getGeneratedKeys()获取自增ID否则无法关联明细表rollback()在catch块中强制执行保证数据一致性。4. 避坑指南95%新手栽在这些细节上附现象、原因与解决步骤4.1 现象启动Tomcat后访问http://localhost:8080/supermarket显示404但http://localhost:8080/supermarket/login.jsp能打开原因web.xml中缺少welcome-file-list配置或index.jsp文件不存在于WebContent/根目录。项目实际入口是login.jsp但默认欢迎页未指向它。解决打开WebContent/WEB-INF/web.xml在web-app标签下添加welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list重启Tomcat。验证访问http://localhost:8080/supermarket应自动跳转到登录页。4.2 现象登录时输入正确账号密码页面提示“用户名或密码错误”但数据库中数据无误原因UserDAO.login()方法中SQL语句拼写错误或密码加密方式不匹配。查看源码发现项目采用明文存储非MD5但DAO中SQL写成WHERE username ? AND password MD5(?)。解决打开src/dao/UserDAO.java定位login()方法将SQL改为String sql SELECT id, username, real_name, phone FROM user WHERE username ? AND password ?;确保数据库user表中password字段存的是明文如123456而非MD5哈希值。重新编译并重启。4.3 现象下单成功后商品库存未减少或减少数量错误如买2件只减1原因OrderServlet中库存扣减循环逻辑错误cartArray解析后未正确获取quantity字段或goodsId传参错位。解决在OrderServlet.doPost()中for循环前添加日志System.out.println(购物车数据 cartJson); // 查看原始JSON结构检查前端传参格式是否为[{goodsId:1,quantity:2},{goodsId:3,quantity:1}]确认item.getIntValue(quantity)调用正确若JSON中是字符串quantity:2需用item.getString(quantity)并Integer.parseInt()在UPDATE goods SET stock stock - ?语句前打印System.out.println(扣减商品ID goodsId 数量 quantity);验证参数。4.4 现象LayUI日期控件点击无反应控制台报WdatePicker is not defined原因WdatePicker.js未被正确引入或路径错误。项目WebContent/js/下有My97DatePicker/文件夹但login.jsp中引用的是../js/WdatePicker.js实际路径应为../js/My97DatePicker/WdatePicker.js。解决打开WebContent/login.jsp找到script typetext/javascript src../js/WdatePicker.js/script改为script typetext/javascript src../js/My97DatePicker/WdatePicker.js/script同时检查WebContent/js/My97DatePicker/下是否存在WdatePicker.js和lang/zh-cn.js中文语言包。4.5 现象导出Excel功能点击后浏览器下载空白文件0字节原因ExportServlet.java中未设置响应头Content-Disposition或response.getOutputStream()被提前关闭。解决打开src/servlet/ExportServlet.java在doGet()方法开头添加response.setContentType(application/vnd.ms-excel;charsetutf-8); response.setHeader(Content-Disposition, attachment;filenameorders_ System.currentTimeMillis() .xls);确保HSSFWorkbook wb new HSSFWorkbook();创建后wb.write(out);执行完毕且out.close()在最后调用不能在try块内提前关闭。5. 进阶改造把课设项目升级为可演示的“准商用”系统含3个实战技巧5.1 技巧一为订单列表增加分页功能不用框架手写PageBean项目原版订单列表/order/list一次性查出所有数据数据量大时页面卡死。我们用最简方式实现物理分页新增PageBean.java工具类放在src/bean/public class PageBeanT { private ListT list; // 当前页数据 private int pageNum; // 当前页码 private int pageSize; // 每页条数 private int total; // 总记录数 private int totalPages; // 总页数 public PageBean(int pageNum, int pageSize, int total) { this.pageNum pageNum; this.pageSize pageSize; this.total total; this.totalPages (int) Math.ceil((double) total / pageSize); this.list new ArrayList(); } // getter/setter 略 }改造OrderDAO.listByPage()public PageBeanOrderInfo listByPage(int pageNum, int pageSize) throws SQLException { // 先查总数 String countSql SELECT COUNT(*) FROM order_info; Connection conn JdbcUtil.getConnection(); PreparedStatement ps conn.prepareStatement(countSql); ResultSet rs ps.executeQuery(); rs.next(); int total rs.getInt(1); rs.close(); ps.close(); // 再查分页数据MySQL limit语法 String sql SELECT id, user_id, total_amount, status, create_time FROM order_info ORDER BY create_time DESC LIMIT ?, ?; ps conn.prepareStatement(sql); ps.setInt(1, (pageNum - 1) * pageSize); ps.setInt(2, pageSize); rs ps.executeQuery(); ListOrderInfo list new ArrayList(); while (rs.next()) { OrderInfo o new OrderInfo(); o.setId(rs.getInt(id)); o.setUserId(rs.getInt(user_id)); o.setTotalAmount(rs.getBigDecimal(total_amount)); o.setStatus(rs.getString(status)); o.setCreateTime(rs.getTimestamp(create_time)); list.add(o); } rs.close(); ps.close(); JdbcUtil.close(conn); PageBeanOrderInfo pb new PageBean(pageNum, pageSize, total); pb.setList(list); return pb; }JSP页面添加分页HTMLorder_list.jsp底部div classpage a hreforder/list?pageNum1首页/a a hreforder/list?pageNum${pb.pageNum 1 ? pb.pageNum - 1 : 1}上一页/a span第 ${pb.pageNum} 页共 ${pb.totalPages} 页/span a hreforder/list?pageNum${pb.pageNum pb.totalPages ? pb.pageNum 1 : pb.totalPages}下一页/a a hreforder/list?pageNum${pb.totalPages}尾页/a /div效果访问/order/list?pageNum2即可加载第2页数据数据库只查10条pageSize10响应速度提升80%。这是课程设计答辩时最直观的“加分项”。5.2 技巧二用Log4j替换System.out.println()实现日志分级与文件输出项目所有日志都用System.out.println()无法区分DEBUG/INFO/WARN且重启后日志丢失。替换为Log4j 1.2.17兼容JDK 1.8添加依赖将log4j-1.2.17.jar放入lib/并在IDEA中Add as Library。创建log4j.properties放在src/下log4j.rootLoggerINFO,Console,File log4j.appender.Consoleorg.apache.log4j.ConsoleAppender log4j.appender.Console.layoutorg.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern%d{yyyy-MM-dd HH:mm:ss} [%p] %c{1} - %m%n log4j.appender.Fileorg.apache.log4j.DailyRollingFileAppender log4j.appender.File.Filelogs/supermarket.log log4j.appender.File.DatePattern.yyyy-MM-dd log4j.appender.File.layoutorg.apache.log4j.PatternLayout log4j.appender.File.layout.ConversionPattern%d{yyyy-MM-dd HH:mm:ss} [%p] %c{1} - %m%n改造任意Servlet如LoginServletimport org.apache.log4j.Logger; public class LoginServlet extends HttpServlet { private static final Logger logger Logger.getLogger(LoginServlet.class); protected void doPost(HttpServletRequest request, HttpServletResponse response) { String username request.getParameter(username); logger.info(用户【 username 】尝试登录); // 替换所有System.out // ... 业务逻辑 if (user ! null) { logger.warn(用户【 username 】登录成功); // WARN级别用于关键事件 } else { logger.error(用户【 username 】登录失败密码错误); // ERROR用于异常 } } }价值答辩时可展示logs/目录下按天生成的日志文件证明系统具备生产环境可观测性面试官问“怎么排查线上问题”你能直接说“看supermarket.2024-06-15.log”。5.3 技巧三为商品图片增加本地上传与显示绕过七牛云等第三方项目商品表goods有image_path字段但前端未实现上传。我们用原生Servlet实现修改goods_add.jsp表单enctypemultipart/form-dataform actiongoods/upload methodpost enctypemultipart/form-data input typefile nameimage acceptimage/* input typesubmit value上传图片 /form创建UploadServlet.javasrc/servlet/protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { Part part request.getPart(image); String fileName Paths.get(part.getSubmittedFileName()).getFileName().toString(); String ext fileName.substring(fileName.lastIndexOf(.)); String newFileName System.currentTimeMillis() ext; // 防重名 // 保存到WebContent/images/ String uploadPath request.getServletContext().getRealPath(/images/); File uploadDir new File(uploadPath); if (!uploadDir.exists()) uploadDir.mkdirs(); part.write(uploadPath File.separator newFileName); // 返回JSON response.setContentType(application/json;charsetutf-8); response.getWriter().write({\code\:200,\url\:\/images/ newFileName \}); } catch (Exception e) { response.getWriter().write({\code\:500,\msg\:\上传失败\}); e.printStackTrace(); } }配置web.xml映射servlet servlet-nameUploadServlet/servlet-name servlet-classservlet.UploadServlet/servlet-class /servlet servlet-mapping servlet-nameUploadServlet/servlet-name url-pattern/goods/upload/url-pattern /servlet-mapping效果上传后返回/images/1718523456789.jpg前端用img src${imageUrl}即可显示。这是课程设计里“独立完成模块”的硬核证据——没有调用任何SDK纯手写文件流处理。从那以后我每次做JavaWeb课设都强制走一遍这三步先跑通基础流程再加日志分级最后补一个可演示的增值功能分页/上传/导出。不是为了炫技而是让代码在答辩现场真正“活”起来——当老师点开订单列表翻到第5页看到日志文件里精确记录着每一笔操作再上传一张商品图实时显示那种“这学生确实搞懂了”的眼神比95分更有说服力。希望帮到你。本文还有配套的精品资源点击获取
返回列表