ARTICLE DETAIL

资讯详情

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

JavaWeb超市管理系统课设:从JSP+Servlet到数据库部署全解析

JavaWeb超市管理系统课设:从JSP+Servlet到数据库部署全解析 简介一份基于JavaWeb的超市管理系统完整源代码与数据库脚本面向正在学习JavaWeb开发的学生、初级开发者及高校实训课程适合作为MVC模式、Servlet、JSP与JDBC综合练习的参考项目也是毕业设计或课程作业的实用范本。资源共150个文件以Java源文件和编译后的class为主33个java、66个class包含18个JSP页面用于动态展示、19个XML配置文件、6个jar依赖库以及SQL数据库脚本和CSS样式压缩包整体仅5.08MB结构紧凑便于快速下载与本地部署。已有424人学习下载项目覆盖用户登录、商品管理、购物车、订单处理等典型业务模块并涉及数据库设计与事务操作能帮助读者理解从请求处理到数据持久化的完整Web开发流程对深入掌握JavaWeb技术栈很有助益。除了可运行的源代码和建表脚本还提供IDE工程文件与说明文档方便直接导入环境查看效果也可作为实训教学和二次开发的拓展基础。1. JavaWeb超市管理系统这个经典课设项目到底解决什么问题期末前两周才翻出这个 .rar是不少人的真实状态里面是完整的 JavaWeb 超市管理系统源代码加一份 MySQL 数据库脚本。它是课设圈的标准模板——JSP 做页面、Servlet 接请求、JDBC 操作数据库把商品进销存、收银台和订单串成一条能现场演示的业务线。它解决的是学完 JavaWeb却不知道增删改查、Session 登录、事务这些点怎么连成一个系统。这个 .rar 给了一个最小但完整的闭环数据库脚本导进去、部署到 Tomcat浏览器里就能看到正在工作的管理系统。适合谁赶课设、想快速跑通完整项目的人想用最短时间复习 JavaWeb 全链路、弄清三层架构真实形态的从业者。下面按我把这类项目从解压到跑通、再到改造升级的路径拆开讲。2. 项目骨架与技术选型JSPServletMySQL 为什么还是课设黄金组合先别急着双击启动。解压完 .rar第一件事是认清目录结构搞明白每一层代码为什么存在。这套技术栈在真实企业里早被 Spring Boot 取代但作为教学单元它还没过时——它把 HTTP 请求、Servlet 生命周期、JDBC 连接、Session 这些底层概念暴露得明明白白Spring Boot 一个注解全包了反而不利于理解请求是怎么进来、怎么出去的。包括黑马的 JavaWeb 笔记在内主流教学路线都保留着 Servlet 这一站原因就在这里。2.1 解压后先认目录src、WebRoot、sql 各管什么SuperMarket/ ├── src/ │ ├── com/supermarket/entity/ # 实体类和表结构一一对应 │ ├── com/supermarket/dao/ # JDBC 数据访问写 SQL 的地方 │ ├── com/supermarket/service/ # 业务层放判断逻辑和事务 │ ├── com/supermarket/servlet/ # 控制器接收请求调 service │ ├── com/supermarket/filter/ # 过滤器管编码和登录拦截 │ └── db.properties # 数据库连接配置 ├── WebRoot/ │ ├── admin/ # 后台 JSP 页面 │ ├── WEB-INF/ │ │ └── web.xml # 部署描述符注册 Servlet/Filter │ └── index.jsp ├── sql/ │ └── supermarket.sql # 建库建表 初始数据 └── lib/ # MySQL 驱动等第三方 jar如果你解压出来的结构类似包名、类名可能不同说明它是标准的 JavaWeb 工程不是把代码揉成一团的 Demo。重点确认三样东西有没有 sql 脚本、lib 里有没有 MySQL 驱动 jar、WEB-INF 下有没有 web.xml。这三样齐全项目大概率能救活。为什么实体类要和表结构一一对应因为 JDBC 查询返回的是 ResultSet你得手动把每一列取出来塞进对象这一层映射关系写清楚页面才能用goods.getName()这种方式取值。而 DAO 层是整套代码里唯一允许出现 SQL 的地方——这个约定守住了后面查问题就是一层一层往上找而不是满项目翻字符串拼接。2.2 三层架构职责边界DAO 只碰 SQLServlet 不碰 SQL// GoodsDao.java —— 数据访问层职责只有一个执行 SQL 并返回结果 public class GoodsDao { private DbUtil db new DbUtil(); // 封装了获取连接的公共类 // 按条件分页查询商品keyword 为空时只做分页 public ListGoods list(String keyword, int page, int pageSize) throws SQLException { StringBuilder sql new StringBuilder( SELECT id, name, price, stock, category_id FROM goods WHERE 11); if (keyword ! null !keyword.trim().isEmpty()) { sql.append( AND name LIKE ?); } sql.append( LIMIT ?, ?); ListGoods list new ArrayList(); // try-with-resources连接、语句、结果集自动关闭避免连接泄漏 try (Connection conn db.getConnection(); PreparedStatement ps conn.prepareStatement(sql.toString())) { int index 1; if (keyword ! null !keyword.trim().isEmpty()) { ps.setString(index, % keyword %); } // 第二页 跳过 (2-1)*pageSize 条很多人这里错位 ps.setInt(index, (page - 1) * pageSize); ps.setInt(index, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(new Goods( rs.getInt(id), rs.getString(name), rs.getBigDecimal(price), rs.getInt(stock), rs.getInt(category_id) )); } } } return list; } }这段代码有四个可以抄走的关键点。第一PreparedStatement 占位符传参天然防 SQL 注入永远不要用字符串拼接的方式拼参数这是课设代码审查必看的地方。第二try-with-resources 自动关闭连接和结果集很多课设被扣分不是因为功能不对而是连接开了一堆永不关闭。第三LIKE 模糊查询的%要自己拼在参数里占位符只认内容不认通配符。第四分页 LIMIT 的偏移量是(page-1)*pageSize第一页正常第二页错位基本就是这里笔误。Servlet 层只做三件事收参数、调 service、决定跳哪个 JSP 还是重定向。永远别在 Servlet 里写 SQL也别在 JSP 里用脚本片段连数据库这是代码分层的第一条红线。service 层在这类课设里经常很薄甚至有人直接跳过但它的存在意义在第四节的收银事务里会体现出来跨表操作的事务必须开在 service不能开在某个 DAO 里。2.3 一次请求的完整流转从 JSP 表单到数据库再回显以新增商品为例。JSP 页面里的表单长这样form action${pageContext.request.contextPath}/goods/add methodpost input typetext namename placeholder商品名称 input typenumber nameprice step0.01 placeholder单价 input typenumber namestock placeholder库存 button typesubmit保存/button /form注意 action 用了${pageContext.request.contextPath}动态拼接上下文路径。如果写死/supermarket/goods/add将来部署名一改立刻 404这是新手最容易踩的路径坑。Servlet 收到请求后的处理// GoodsAddServlet.java protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); // 必须在读取任何参数之前执行 String name req.getParameter(name); BigDecimal price new BigDecimal(req.getParameter(price)); int stock Integer.parseInt(req.getParameter(stock)); Goods goods new Goods(); goods.setName(name); goods.setPrice(price); goods.setStock(stock); GoodsService service new GoodsService(); service.add(goods); resp.sendRedirect(req.getContextPath() /goods/list); // 重定向前端再查一次 }setCharacterEncoding(UTF-8)这一行必须在读取任何参数之前调用后端拿到的是正常中文还是乱码在这行就定了。POST 参数的解码依赖它而 GET 参数的解码看的是 Tomcat 的 URIEncoding两码事后面避坑章会细说。整个数据流转要在脑子里形成一条链表单里的 name 属性 → Servlet 里 getParameter 的 key → Service 组装对象 → DAO 里 PreparedStatement 的参数 → MySQL 执行 → 重定向回列表页再查一次。这条链路理解透了后面看 SSM、Spring Boot 的项目都是同一套逻辑换了一层皮。3. 从 .rar 到跑起来数据库初始化与 IDEA 运行配置全流程技术栈看懂了接下来是让项目在你自己电脑上跑起来。我习惯的顺序是先装环境再导数据库最后配 Tomcat。顺序反了会浪费大量时间在排查代码没问题但就是起不来的玄学问题上其实八成是环境不匹配。3.1 环境版本配合JDK 8 Tomcat 8.5 MySQL 5.7 最稳课设代码大多是早年按 Java 6/7 写的用太新的环境反而麻烦。我推荐的组合如下组件推荐版本说明JDK1.8老代码在 JDK 8 下编译最稳兼容性最好Tomcat8.5和 JDK 8 配合支持 Servlet 3.1MySQL5.7与旧版驱动兼容少踩认证插件和时区的坑IDEA2020 之后的版本社区版也能配置 Tomcat不必买旗舰版如果你本机已经是 MySQL 8.0不是不能跑但要先处理两件事驱动换成 mysql-connector-java 8.x连接 URL 加serverTimezoneAsia/Shanghai否则会报时区错误。Tomcat 10 的包名从 javax.servlet 改成了 jakarta.servlet老课设代码直接编译不过建议别用它。3.2 导入 SQL 脚本命令行和 Navicat 两条路建库建表的脚本一般是CREATE DATABASE supermarket加一串建表语句。导入最快的方式是 MySQL 命令行# 脚本里已包含 CREATE DATABASE 时直接整体导入 mysql -u root -p sql/supermarket.sql如果脚本里只有建表语句需要先手动建库再导入mysql -u root -p -e CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARSET utf8mb4 mysql -u root -p -D supermarket sql/supermarket.sql导入完别急着启动项目先花 30 秒验证三件事库在不在、表全不全、初始数据有没有。在 mysql 客户端或 Navicat 里执行SHOW DATABASES; -- 确认 supermarket 库存在 USE supermarket; SHOW TABLES; -- 数一数表数量对不对 SELECT * FROM admin LIMIT 5; -- 看初始账号是否导入成功这一组查询是 mysql 数据库里最常用的几条命令30 秒做完能省掉后面两小时登录页永远提示密码错误的排查。初始账号密码一般在 SQL 脚本的 INSERT 语句里或者在 README 注释里导完数据先查一眼记下来。用 Navicat 的话路径是连接本机 MySQL → 右键数据库 → 运行 SQL 文件 → 选sql/supermarket.sql→ 开始。注意如果脚本里没有USE supermarket;这句导入前要先手动建同名库否则表会建到默认库里项目照样连不上。3.3 改数据库连接配置db.properties 是项目的命门这类项目通常把连接参数抽在一个 properties 文件里常见内容如下jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://127.0.0.1:3306/supermarket?useUnicodetruecharacterEncodingutf8 jdbc.usernameroot jdbc.password123456四个参数逐个说清楚。driver 是驱动全类名MySQL 5.x 用com.mysql.jdbc.DriverMySQL 8.x 要换成com.mysql.cj.jdbc.Driver。url 里的 3306 是 MySQL 默认端口你本机改过端口必须同步改。useUnicodetruecharacterEncodingutf8控制中文字符集这两项不写入库的中文大概率变问号。password 要填你自己 MySQL 的密码不是脚本里注释写的那个假密码。改完配置先做一个最小验证在 IDEA 里写一个三行的 main 方法直接调一次 DbUtil 拿连接。这一步能跑通说明数据库侧没问题后面再报错就集中往部署侧排查节省大量时间public static void main(String[] args) { Connection conn new DbUtil().getConnection(); // 断点或打印看是否非 null System.out.println(conn ! null ? 连接成功 : 连接失败); }3.4 IDEA 配置 TomcatArtifacts 和 Deployment 是最容易翻车的一步IDEA 跑 JavaWeb 项目不是点个运行按钮那么简单。它要把编译后的 classes、JSP 页面、第三方 jar 打成一个 Web 应用结构再塞进 Tomcat 的 webapps。我的固定步骤是这样打开 Project Structure → Artifacts → 点 → Web Application: Exploded把 Output directory 指到你工程对应的 WebRoot 目录。Edit Configurations → 点 → Tomcat Server → Local选本地 Tomcat 安装目录。切到 Deployment 标签页 → 点 → Artifact选上一步建好的 Web 应用。Application context 填/supermarket访问路径就是http://localhost:8080/supermarket/。启动控制台出现Server startup in xxx ms说明部署成功。最容易翻车的是 WebRoot/WEB-INF/lib 里的 jar 没有被打进 Artifact。很多人的 jar 明明放在 lib 目录但启动后照样报 ClassNotFound。这时要在 Artifact 设置里手动把 lib 下的 jar 加入右键 Put into /WEB-INF/lib。提示每次调整过 Artifact 打包内容后务必执行 Build → Rebuild Project。IDEA 对 Artifact 的增量更新偶尔不生效脏包会引发各种奇怪的 404 和 ClassNotFound重建一次通常就解决。4. 核心业务模块实现商品管理、收银台与库存联动的代码路径这类超市系统的业务模块看着多本质是同一套 CRUD 模板的重复商品、供应商、会员、类别全是列表页 新增/编辑表单 删除按钮。真正有技术含量的是两处——收银台的库存扣减事务以及登录拦截。这两处做得稳演示时无论老师怎么点都不会翻车。4.1 商品增删改查一套 CRUD 模板吃遍所有模块以删除为例最典型的写法是 Servlet 里直接调 service// GoodsDeleteServlet.java protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int id Integer.parseInt(req.getParameter(id)); // 列表页链接带过来的 GoodsService service new GoodsService(); int rows service.delete(id); // 删完重定向回列表页面显示的永远是最新数据 resp.sendRedirect(req.getContextPath() /goods/list?msg (rows 0 ? success : fail)); }删除用 doGet 是因为列表页里的删除入口就是普通链接点击。对应的 JSP 片段a href${pageContext.request.contextPath}/goods/delete?id${goods.id} onclickreturn confirm(确定删除该商品吗)删除/aonclick 里的 confirm 是前端最后一道防线。很多课设删数据零提示演示时手滑点到就尴尬了。严谨的生产做法是删除用 POST 加表单提交但课设场景普遍用链接加 confirm够用。新增和修改的后端骨架完全一样新增是INSERT INTO goods VALUES (?,?,?,?)修改是UPDATE goods SET name?, price?, stock? WHERE id?换的只是 SQL 语句和参数个数。你照着 DAO 里已有的 insert 复制一份改成 update一个修改功能十分钟就能写完。改完记得把两个隐藏坑处理掉价格字段用BigDecimal接收别用 float库存字段用Integer.parseInt前先判空否则传空字符串会抛 NumberFormatException。4.2 收银台扣库存事务一定要放在 service 层收银台是核心场景前端把购物车提交过来后端要做两件事——写入销售单、扣减商品库存。这两步必须同生共死是一个事务。很多课设只写了插入销售单扣库存漏了演示时商品库存永远不变业务上就是漏洞。事务的正确写法是把 Connection 的控制权从 DAO 收到 service并且让多个 DAO 复用同一个连接// SaleService.java —— 事务边界在 serviceDAO 只负责执行 SQL public void checkout(ListCartItem items) throws SQLException { Connection conn new DbUtil().getConnection(); conn.setAutoCommit(false); // 先关自动提交 try { SaleDao saleDao new SaleDao(conn); // 关键DAO 复用同一连接 GoodsDao goodsDao new GoodsDao(conn); for (CartItem item : items) { saleDao.insert(item); // 写销售明细 int rows goodsDao.decreaseStock(item.getGoodsId(), item.getNum()); if (rows 0) { throw new RuntimeException(库存不足: item.getGoodsName()); } } conn.commit(); // 全部成功才提交 } catch (Exception e) { conn.rollback(); // 任何一步失败全部回滚 throw e; } finally { conn.setAutoCommit(true); // 恢复现场避免脏状态 conn.close(); } }这段是整个超市系统里最值钱的代码。答辩老师十有八九会问你怎么保证并发情况下库存不超卖答案在 DAO 的条件更新里public int decreaseStock(int goodsId, int num) throws SQLException { String sql UPDATE goods SET stock stock - ? WHERE id ? AND stock ?; // 返回 0 表示受影响行数为 0说明现有库存不够 }用受影响行数判断库存是否充足比先 SELECT 查库存 → 判断 → 再 UPDATE安全得多。后者在高并发下会读到旧值两个请求同时判断库存够然后一起扣成负数。条件更新把判断和操作合并成一条原子 SQL从根上挡住了超卖。这个点讲清楚比堆十个功能页面都加分。4.3 登录拦截一个 Filter 挡住所有未登录请求Session 登录是 JavaWeb 必考点。登录成功的动作很简单// LoginServlet.java 登录成功分支 req.getSession().setAttribute(adminUser, admin); resp.sendRedirect(req.getContextPath() /admin/index.jsp);然后在 web.xml 注册一个 Filter 拦截后台路径// LoginFilter.java —— 未登录一律跳回登录页 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(); Object user session.getAttribute(adminUser); String uri req.getRequestURI().substring(req.getContextPath().length()); // 放行登录页、登录接口和静态资源其余全部拦截 if (uri.startsWith(/login) || uri.startsWith(/css) || uri.startsWith(/js) || uri.startsWith(/images) || user ! null) { chain.doFilter(request, response); } else { resp.sendRedirect(req.getContextPath() /login.jsp); } }Filter 的 url-pattern 如果写/*一定要记得放行 css、js、images 这些静态资源否则登录页加载出来没有样式CSS 全部 404页面丑得没法演示。Session 超时是个隐藏坑Tomcat 默认 30 分钟演示到一半跳回登录页是正常机制不是 Bug。想延长可以在 web.xml 里加 session-config 配置或临时把 Tomcat 的全局 session 超时调大。5. 跑通这套系统的避坑记录驱动、乱码与部署路径的 5 个实战问题这类课设项目九成以上的报错集中在少数几个点上而且报错信息几乎一样。下面按我实际帮人排查的经验把高频问题按现象 → 原因 → 解决列清楚照着定位比盲目重装环境靠谱得多。5.1 现象启动报 ClassNotFoundException: com.mysql.jdbc.Driver启动 Tomcat 时日志抛出ClassNotFoundException: com.mysql.jdbc.Driver或者启动正常第一次点查询就报这个。原因MySQL 驱动 jar 没有打进 Web 应用的 WEB-INF/lib。很多人以为把 jar 放进项目目录就行但 IDEA 打包 Artifact 时没把它算进去运行期根本找不到这个类。解决Project Structure → Artifacts → 选你的 Web 应用 → 在 Available Elements 里找到 mysql-connector 相关 jar → 右键 Put into /WEB-INF/lib → Rebuild Project。验证方法启动后到 Tomcat 的部署目录看一眼WEB-INF/lib 下是否有 mysql-connector-java 的 jar 文件。5.2 现象页面或数据库里的中文全是问号登录页显示正常但新增的商品名称入库后变成???或者列表页中文乱码。中文乱码是 JavaWeb 里最玄学的问题之一因为它不是单一原因。原因三层里至少一层没配对——JDBC URL 少了characterEncodingutf8、JSP 页面没有声明 UTF-8、Tomcat 的 GET 请求解码不是 UTF-8、数据库表本身的字符集是 latin1。解决按顺序逐层排查。先在 JDBC URL 里补上useUnicodetruecharacterEncodingutf8JSP 头加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Tomcat 的 conf/server.xml 里 Connector 加URIEncodingUTF-8最后确认表和库的字符集老脚本可能建成了 latin1执行ALTER TABLE goods CONVERT TO CHARACTER SET utf8mb4;转换一次。注意Servlet 里的setCharacterEncoding必须在读取任何参数之前调用。但这条只对 POST 请求生效GET 请求里的中文参数是由 Tomcat 的 URIEncoding 决定的和 Servlet 里那行无关这是很多人忽略的分工。5.3 现象Tomcat 启动报 Port 8080 already in use控制台红字报Port 8080 required by Tomcat is already in use或者启动后一直卡住。原因8080 被占用了可能是之前误杀了 IDEA 但 Tomcat 进程没退干净也可能是别的服务用了这个端口。解决Windows 下先查占用netstat -ano | findstr 8080找到 PID 后taskkill /F /PID pid或者干脆改端口编辑 Tomcat 的 conf/server.xml把 Connector 的 port 从 8080 改成 8082。改完以后访问地址跟着变项目代码不用动。IDEA 里改端口的位置在 Run Configuration 的 HTTP port 设置。5.4 现象启动成功但访问任意页面都是 404Tomcat 正常起来了日志没有异常但浏览器访问http://localhost:8080/或者某个功能页就是 404。原因八成是部署的 Application context 和页面里写死的路径对不上。比如代码里全是/supermarket/index.jsp但你的 Application context 设成了/实际路径变成了/supermarket/supermarket/index.jsp。解决统一路径只留一处。推荐把 Application context 设为/supermarket所有 JSP 里的链接一律用${pageContext.request.contextPath}开头不写死任何一级路径。改完重启再访问http://localhost:8080/supermarket/验证。5.5 现象MySQL 8.0 连接失败报 Communications link failure本机 MySQL 是 8.0按老配置连不上报Public Key Retrieval is not allowed、Access denied或者时区相关异常。原因MySQL 8.0 默认认证插件是 caching_sha2_password老驱动 5.x 不认识另外 8.0 驱动要求 URL 显式指定时区否则把本地时区当异常抛出来。解决两条路任选一条。升级驱动为 8.xdriver 类名改成com.mysql.cj.jdbc.DriverURL 加serverTimezoneAsia/Shanghai或者把 root 用户改回旧认证方式执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;。课设场景推荐后者改动最小还能保留旧驱动。6. 把课设升级成简历项目连接池替换与关键路径验证跑通只是及格线想把它变成简历上能讲的项目有两个高性价比的改造方向。第一个把 DbUtil 里的裸 JDBC 换成 Druid 连接池。老写法每次 getConnection 都新建连接课设演示没人关注但面试必问。改造集中在 DbUtil 一个类里public class DbUtil { private static DruidDataSource dataSource new DruidDataSource(); static { dataSource.setUrl(jdbc:mysql://127.0.0.1:3306/supermarket ?useUnicodetruecharacterEncodingutf8); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setInitialSize(5); // 初始连接数 dataSource.setMaxActive(20); // 最大活跃连接数 } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); // 拿到的连接来自池可复用 } }改造后注意两点连接用完后依然要 close但这时的 close 只是归还池子不是真断开事务里 commit、rollback 的写法完全不变。给 Druid 配一个监控页还能在答辩时演示连接池的活跃连接数变化属于加分项。提示连接池里的 close 只是归还连接不是真正关闭。所以 finally 里关连接的代码不但不能省反而更关键——不归还就相当于慢性泄漏。第二个给关键操作加操作日志。简历里写系统包含操作日志是一个具体的能力点实现也不复杂建一张 operate_log 表id、admin_id、action、detail、create_time在删除商品、结账这些敏感操作的 service 方法里各插一条记录。不用做得重能说清楚谁在什么时间删了哪个商品、结了多少金额的账就够了。最后是我的一个习惯交课设前把功能按登录 → 新增商品 → 进货 → 收银 → 查看订单的顺序完整走三遍同时开着 MySQL 命令行观察数据变化——新增后 goods 表多一行收银后库存减、订单表增。数据在库里真实变动比任何截图都有说服力。答辩时主动说出库存扣减用了条件更新和事务从根上防止超卖这一句话的含金量超过你堆出来的十个功能页面。把这套 JavaWeb 超市管理系统从解压、导库、部署到改造完整过一遍你对增删改查、Session、事务、Filter 的理解就不再是课本上的名词而是一条条亲手调过的真实代码路径。希望帮到你。本文还有配套的精品资源点击获取
返回列表