资讯动态

JavaWeb超市管理系统毕业设计:Servlet+JSP+JDBC分层实现与部署避坑指南

发布时间:2026/10/9 12:52:29 来源:尧图企业网站定制
简介一套基于 JavaWeb 的超市管理系统毕业设计项目包含可运行源码与数据库脚本适合计算机、通信、人工智能、自动化等相关专业学生、教师或从业者作为毕业设计、课程设计或期末大作业参考也可作为 Java 入门者的进阶练手项目。系统围绕商品管理、会员管理、供应商管理、结账与用户登录等模块展开采用 JSP 与 Servlet 的分层设计代码经调试测试可正常运行整体结构清晰便于理解 JavaWeb 项目从数据访问到页面渲染的完整流程。压缩包为 zip 格式共 386 个文件作品中以 49 个 Java 源文件、56 个 JSP 页面和 98 个 class 文件为核心同时提供 SQL 数据库脚本及 CSS、SCSS、JS 等前端资源包体大小约 10.36MB。这套系统曾作为个人毕业设计获得 98 分评价目前已有 156 人浏览学习。读者可在部署运行后对照源码二次开发如扩展库存预警、销售统计、供应商对账等模块。1. 中式超市管理系统该有的样子先搞清楚这份 javaweb 毕业设计值不值得花时间拿到这份 javaweb 超市管理系统时我第一反应是“又一个三层架构大作业”但拆完代码发现它其实是那种答辩特别稳的项目Servlet 管请求、JSP 渲染页面、DAO 封装 JDBC再加一套可直接导入的数据库脚本。见过太多毕设死在“框架堆得华丽但跑不起来”这个反过来——代码朴素但逻辑闭环完整商品、会员、供应商、收银、用户五条业务线全部打通用应付“你这系统怎么设计的”这类答辩提问比花架子 Spring Boot 全家桶好使。尤其它的收银模块涉及事务处理这是多数课程设计里最容易偷懒、也最容易被老师追问的地方。如果你是计算机、通信、自动化相关专业的学生需要一份能讲清楚来龙去脉的 JavaWeb 项目当毕设底稿或者正在补 Servlet/JSP 底层链路的知识这个资源值得拆开看一看。2. 从五个 DaoImpl 反向拆解项目看懂 Servlet DAO JSP 的老三层是怎么协作的2.1 为什么这套老技术栈反而是毕设的加分项现在培训机构推的都是 Spring Boot MyBatis-Plus但如果你拿这种框架交毕设最容易翻车的场景恰恰是答辩老师一句“你这个接口是怎么被路由到 Controller 的”很多同学就卡住了。而 Servlet JSP JDBC 这套组合虽然“老”但它把一次 HTTP 请求的完整路径摆在了明面上——浏览器发请求Tomcat 根据web.xml或注解找到 Servlet 的doGet/doPostServlet 调 DAO 接口DAO 实现类用 JDBC 拼 SQL 查 MySQL 数据库再把结果setAttribute后forward给 JSP 渲染。这个链路每一步都能展开讲也最容易用“自动挡换成手动挡”的类比跟老师解释清楚。再看这套系统的类结构CheckoutServlet.class、UserDaoImpl.class、ProductDaoImpl.class、VipDaoImpl.class、ProviderDaoImpl.class直接摆在那里说明它没有引入额外框架就是最原始的 JavaWeb 分层。这类项目还有另一个隐性福利代码量可控。一个有正常课业压力的学生两周内把它读透并改造出自己的功能模块比从零写一个 Spring Boot 项目要现实得多。所以选这套技术栈不是落后是策略。2.2 通过类名反推数据库表结构文件清单里的信息量拿到资源先别急着跑先看那五个类名项目的数据模型基本就出来了。每个DaoImpl背后至少对应一张主表和一组增删改查方法这是我拆这类项目时最爱用的“逆向读项目”方式类名对应的业务表常见设计核心职责UserDaoImplt_user用户登录、密码校验、权限区分ProductDaoImplt_product商品增删改查、库存更新、按名称模糊搜索VipDaoImplt_vip会员开卡、充值、折扣查询ProviderDaoImplt_provider供应商信息维护、进货记录CheckoutServlett_sale_record/t_product收银结算、减库存、生成销售记录那数据库脚本里常见的字段就是t_user(id, username, password, role)t_product(id, name, price, stock, provider_id, is_delete)t_vip(id, name, phone, score, discount)t_provider(id, name, contact, phone)。其中is_delete是我特别留意的字段——正规一点的超市管理系统做删除商品时不会物理删行而是软删除把is_delete置 1查询时统一带WHERE is_delete 0过滤。如果你的资源里数据库脚本有这一列说明作者写代码时有真实业务意识项目质量就有底了。搞清楚表结构之后整个项目的运转逻辑就清晰了营业员登录系统先维护好供应商和商品顾客来结账时输入会员号享受折扣CheckoutServlet在当前线程的事务里完成减库存和写销售流水。这套逻辑放在课程设计里已经能覆盖“超市管理系统”这个题目的绝大多数功能点。2.3 把项目导入 IDEA 并按序启动本文的操作步骤我拿到这类源码包的习惯是不急着双击web.xml先按下面五个步骤把运行环境固定下来避免后面踩环境坑时到处猜。第一步装 JDK 8注意是 JDK 8 而不是 11 或 17老项目对高版本 JDK 的兼容性常出问题第二步装 Tomcat 8.5 或 9.0装完后在 IDEA 里 Setting 配置 Application Servers第三步装 MySQL 5.7 或 8.0如果本机已经装了 8.0 也没关系驱动和连接串注意一下即可第四步把数据库脚本导入命令行方式最直接mysql -u root -p supermarket /path/to/supermarket.sql用-p后回车再输密码比直接写在命令行里安全。后面是 SQL 脚本的绝对路径Windows 下注意路径不要带中文否则可能出现读取乱码或找不到文件的诡异报错。导入完成后用show tables;验证一下表是否存在别跳过这步至少能确认脚本执行没有静默失败。第五步打开项目里的数据库连接配置最常见的是一个db.properties文件或DBUtil.java改成本机能连的参数。这里给出一个典型的DBUtil.java长这样public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/supermarket ?useSSLfalsecharacterEncodingUTF-8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }如果你的 MySQL 是 5.7需要把驱动类名改回com.mysql.jdbc.Driver如果是 8.0保持上面这个就行但serverTimezoneAsia/Shanghai必须加否则数据库连接会直接报时区错误。URL 里的characterEncodingUTF-8同样不能删不然 JSP 页面显示商品名称时就是一片问号。配好后在 IDEA 里添加 Tomcat 运行配置Deployment 那一栏选war explodedApplication context 填/supermarket或项目里实际配置的路径点击运行。这里有个容易漏的操作Tomcat 配置里要把项目的依赖包加进去否则启动日志会报ClassNotFoundException因为 Tomcat 没有加载项目的类。启动后浏览器访问http://localhost:8080/supermarket/login.jsp看到登录页说明环境通了。3. 把五条业务线逐条拆开登录、商品、收银台、会员和供应商的实现逻辑3.1 登录与权限UserDaoImpl是入口也是最容易被忽视的安全短板很多课程设计的登录模块就一句SELECT * FROM user WHERE username ? AND password ?能通就万事大吉。但拆这份项目时你会发现关键问题往往不在功能通不通而在这么写能不能禁得起提问。从安全角度讲用PreparedStatement做参数化查询是底线直接拿字符串拼 SQL 的写法会在答辩上被老师一问一个准。下面是一个典型的UserDaoImpl.login()方法写法public User login(String username, String password) { String sql SELECT * FROM t_user WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setRole(rs.getString(role)); return user; } } } catch (SQLException e) { e.printStackTrace(); } return null; }这里?占位符配合ps.setString()能防 SQL 注入这是底线。登录成功后session.setAttribute(user, user)存的是User对象而不是散装用户名后面 JSP 页面判断权限时直接从 session 取对象的role字段比反复查库高效也不容易出错。角色字段通常分admin和cashier两种管理员能进商品维护页面收银员只能进收银台不想加 Spring Security 这类框架时在 JSP 顶部加一个c:if test${sessionScope.user.role admin}做菜单显隐就够课程设计用了。我一般会在登录代码里特别留意一点校验失败时不要提示“用户名不存在”或“密码错误”这种区分性信息统一返回“用户名或密码错误”防止被拿去探测账号有效性。这个细节写进答辩讲稿里往往会让老师觉得你有安全意识。3.2 商品管理软删除和分页查询是两个高频提问点ProductDaoImpl是业务量最大的一个 DAO商品新增、编辑、下架、查询四件事都要在这里完成。新增商品的 SQL 相对直白注意字段别漏provider_id关联供应商表这是一个外键逻辑但很多课程设计会漏掉。真正值得讲的是商品列表的展示一旦商品数据超过几十条全量SELECT *就不现实分页几乎是超市系统的刚需。分页的核心就是 LIMIT 加两个参数SELECT * FROM t_product WHERE is_delete 0 LIMIT ?, ?;第一个?是偏移量(pageNum - 1) * pageSize第二个?是每页条数pageSize。前端 JSP 页面用一个c:forEach循环遍历传给页面的列表数据底部是两个按钮“上一页”和“下一页”Servlet 从 request 参数里拿pageNum做边界判断。这里的坑在于第一页时pageNum - 1会变负数所以进方法第一步就该检查pageNum 1时强行置为 1。我见过不少项目在分页上翻车全是因为没有对页码做下限校验最后一页翻过头时列表直接空白。再就是删除操作。物理删除DELETE FROM t_product WHERE id ?写起来简单但意味着销售记录表里关联的商品名和价格会失去源头将来查历史流水时数据对不上这是实际业务里非常忌讳的事。很多课程设计为了图省事就这么写了答辩时老师问“你这个超市的销售历史和商品档案怎么追溯”当场就接不上话。所以作者在t_product里设计is_delete字段、查询统一带条件这一手是值得你写进讲稿里的。如果拿到的源码不是软删除那我建议动手改一版把delete方法改成UPDATE t_product SET is_delete 1 WHERE id ?改动量不大但能体现真实业务思考。3.3 收银台事务CheckoutServlet是整套系统的含金量所在再来谈这个项目最有份量的一个模块。超市收银不是简单的“算个总价”它牵一发动全身——顾客买了三件商品你要同时完成三件事计算折扣后的总金额、扣减对应商品的库存、向销售记录表插入一条流水。这三件事必须一起成功或一起失败这就是数据库事务的经典场景。而很多课程设计在收银这块其实就是做个“购物车求和”完全没考虑一致性。这份项目里单独拆出一个CheckoutServlet而不是塞在别的 Servlet 里说明作者在架构上是有意识的。收银核心逻辑我把它写成伪代码加注释的框架方便你对照着读源码protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String vipId req.getParameter(vipId); String[] productIds req.getParameterValues(productId); String[] quantities req.getParameterValues(quantity); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 查会员折扣率 double discount 1.0; if (vipId ! null !vipId.isEmpty()) { discount getVipDiscount(conn, vipId); } // 2. 循环扣减库存并计算总价 double total 0; for (int i 0; i productIds.length; i) { int pid Integer.parseInt(productIds[i]); int qty Integer.parseInt(quantities[i]); double price getProductPrice(conn, pid); // 减库存前先校验库存够不够 if (!reduceStock(conn, pid, qty)) { throw new RuntimeException(库存不足); } total price * qty; } total total * discount; // 3. 写销售流水 insertSaleRecord(conn, total); conn.commit(); // 所有操作成功提交事务 resp.sendRedirect(checkout_success.jsp); } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } req.setAttribute(errorMsg, e.getMessage()); req.getRequestDispatcher(checkout.jsp).forward(req, resp); } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }这段代码里最关键的是conn.setAutoCommit(false)和conn.commit()之间的代码块——中间任何一步抛异常rollback()就会把前面已经执行的扣库存操作全部撤销。逻辑上我先校验库存再扣减避免出现卖超的情况productId和quantity用两个数组接收是因为页面上多个相同 name 的隐藏字段提交过来时getParameterValues拿到的是字符串数组必须一一对应取。最后用resp.sendRedirect而不是 forward 跳转到成功页这个是防止刷新页面重复提交的关键——F5 刷新一个 POST 请求页面会重新执行一次结账重复扣库存这是老项目最常见的生产事故。如果你在自己项目里看到结账后没做重定向这个坑必须补上补法就是上面这种“PRG 模式”POST-Redirect-GET。3.4 会员与供应商两条副线分数差距都在细节里VipDaoImpl和ProviderDaoImpl看起来是配角但毕设分数差往往就差在这里。会员模块最常见的功能是开卡和充值开卡时设置初始积分充值时按金额累计积分积分反过来可以兑换小礼品或抵扣现金。这里有个交互细节收银员结账时输入会员手机号就能查到折扣率而不是每次都要手动选会员 ID这个查询通常用SELECT discount FROM t_vip WHERE phone ?。很多做得敷衍的项目会在结账页面放一个下拉框列出所有会员几十个还可以上百个就会卡到没法用这个对比你可以写进论文的“系统优化”部分。供应商模块的核心价值在于进货。新增一个供应商时联系电话、地址等字段要加非空校验商品表的provider_id下拉框数据就是从t_provider查出来的如果页面显示的是供应商 ID 而不是名字JSP 里value${product.providerId}显示时需要做一个 ID 到名称的映射一般是在查商品时直接JOIN t_provider查出名字避免在页面里循环比对。我在看这类代码时会特别留意 DAO 层有没有做结果的 map 映射很多同学干脆把结果集直接塞给 JSP 用字段定义乱七八糟这是需要调整的地方public ListProvider findAll() { String sql SELECT id, name, contact, phone FROM t_provider WHERE is_delete 0; ListProvider list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Provider p new Provider(); p.setId(rs.getInt(id)); p.setName(rs.getString(name)); p.setContact(rs.getString(contact)); p.setPhone(rs.getString(phone)); list.add(p); } } catch (SQLException e) { e.printStackTrace(); } return list; }这段代码的教训是列的读取一定要用别名对齐getInt(id)而不是getInt(1)这样 SQL 里字段顺序调整时不用改代码抗变更能力要强得多。另外该类查出的Provider对象可以直接复用在商品下拉框的渲染里一对多关系维护在Product包含Provider引用上读源码时可以留意作者用的是关联查询还是二次查库。4. 部署与运行避坑老 JavaWeb 项目最容易踩到的五个坑4.1 启动直接ClassNotFoundExceptionTomcat 页面全报错现象Tomcat 一启动日志里报ClassNotFoundException: com.mysql.cj.jdbc.Driver或者项目类的其他包找不到。原因基本都是 IDEA 里 Tomcat 配置的 deployment 没有把项目的webapp目录和依赖的 lib jar 挂上去Tomcat 运行时根本看不到你的类和驱动。解决项目右键 Open Module SettingsArtifacts 里确认选的是war exploded然后在 Available Elements 里把项目依赖的 jar 右键加进 WEB-INF/lib如果是老项目还要检查 Library 里有没有配置 Tomcat 自带的 servlet-api。这个配置一次性搞定后面很少再出问题。4.2 连接时报Public Key Retrieval is not allowed现象MySQL 8.0 下 JDBC 连接报Public Key Retrieval is not allowed或者Access denied for user。原因MySQL 8 默认的caching_sha2_password认证插件要求客户端支持公钥检索而老驱动 JDBC 默认不允许同时驱动类名也从com.mysql.jdbc.Driver换成了com.mysql.cj.jdbc.Driver。解决把连接串改成jdbc:mysql://localhost:3306/supermarket?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai这是 MySQL 8 连接最完整的配置缺哪段都可能在后面报出不同错误。4.3 结账后按 F5 刷新库存重复扣减现象收银台结账成功后手滑按了 F5再查库存变负了销售流水里也多了一条重复记录。原因表单 POST 提交到 ServletServlet 处理完直接 forward 到 JSP刷新就重发了请求。解决按前面 3.3 的方式改成 POST-Redirect-GETresp.sendRedirect(success.jsp)让成功页面变成一个全新 GET 请求同时可以在数据库给销售流水表加一个(order_no)唯一约束做最后兜底双保险。4.4 页面显示中文全是问号或乱码现象商品名称、供应商名称在页面上显示成???或一堆乱码。原因三层编码不一致——数据库表是utf8但 JDBC 连接串没加characterEncodingUTF-8JSP 页面本身也没有pageEncoding标签。解决连接串加上characterEncodingUTF-8同时在每个 JSP 顶部加% page contentTypetext/html;charsetUTF-8 languagejava %数据库建表统一用DEFAULT CHARSETutf8。这是一套联动配置只改其中一处其他两处还是会出问题。4.5 导入 Eclipse 后 JSP 报错javax.servlet不存在现象用 Eclipse 导入项目打开 JSP 文件满屏红叉报javax.servlet cannot be resolved to a type。原因Eclipse 里没有给项目配置 Tomcat RuntimeServlet 相关类就不在编译路径里。解决项目 Properties → Targeted Runtimes → 勾选本机的 Tomcat红叉会立刻消失。另外老项目可能会用到javax.servlet.jsp.jstl标签库如果.jsp文件里c:forEach标签报红记得把jstl.jar也加到 WEB-INF/lib 下。这几条是我拆老 JavaWeb 项目时重复踩过的坑基本都是环境或编码问题而不是业务逻辑问题。拿到的项目如果遇到类似报错对照上面逐条排查比去论坛里搜完整错误码省很多时间。5. 从能跑到能改用一份验证清单验收项目再给它升级两个硬核功能面对一份二手毕设最怕的不是看不懂代码而是不知道它到底是不是“真的能跑”。我每次拿到这类项目都会先走一遍全链路业务验证而不是只看登录页能不能出。五分钟跑完一条完整流程比读十遍代码更能判断项目底细步骤操作预期结果1用 admin 账号登录跳转到后台主页顶部显示管理员菜单2供应商管理里新增一条记录列表新增成功无报错3商品管理里添加一个新商品选刚才的供应商商品列表能看到新商品库存为设定值4会员管理里开一张会员卡充值 100 元积分累计到对应金额5拿着新商品去收银台结账输入会员号折扣生效库存减少销售流水新增一条6F5 刷新结账成功页没有重复扣库存因为用了 PRG走完一遍发现问题就当场修比被动等着答辩时被老师发现强得多。走过这条链路之后如果想在答辩时多拿点分我给两个改动成本不高但显得有想法的升级方向。第一个是给商品列表加一个“库存预警”入口在ProductDaoImpl里加一个方法SQL 一行就能完成SELECT * FROM t_product WHERE stock 10 AND is_delete 0;在主页加一个带红点的提醒按钮点击后转发到一个专门的 JSP 展示低库存商品列表这就是“库存预警功能”。第二是把DBUtil里用DriverManager.getConnection()的方式换成 Druid 连接池核心改动在DBUtil类和druid.properties配置但能让项目在答辩时多一个“性能优化”的话术远比“加了缓存”这种虚无的表述能落地。拆这个项目的过程里我最大的体会是毕设分数和框架新不新没什么关系关键看你能不能把每条业务线的来龙去脉讲清楚出问题时能不能快速定位到属于哪一层。从那以后我每次拿到一份 Servlet 老项目都强制自己先走完整业务闭环再去读具体代码顺序反了很容易被细节牵着走。这份资源和这套排查思路希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑