ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Java Web超市系统实战:Servlet+JDBC+MySQL课设级工程

Java Web超市系统实战:Servlet+JDBC+MySQL课设级工程 简介这是一套基于Java语言与MySQL数据库开发的超市管理实战项目面向Java初学者及Web开发入门者解决中小型超市商品进销存、员工权限分离与经营数据统计等核心管理需求。资源包共378个文件含49个Java源码、56个JSP页面、98个编译后Class文件以及CSS/JS/SCSS等前端资源和MySQL建表SQL脚本完整覆盖前后端代码、数据库设计与界面样式压缩包大小为10.37MB。已有2384人学习下载体现了其在教学实践与课程设计中的广泛认可。用户可直接导入Eclipse运行获得包含管理员商品/供应商/员工管理与收银员销售结算/库存实时更新双角色界面的可执行系统并通过源码深入理解DAO层封装、Servlet控制逻辑、JSP页面交互及MySQL多表关联查询等关键开发实践。1. Java超市管理系统不是Demo是能跑通进销存双角色权限的真实课设级工程你手头这份「Java超市管理系统EclipseMySQL」不是网上泛滥的“Hello World式超市demo”而是一套完整闭环、带真实业务逻辑、可本地部署运行的课设级实战项目——它不依赖Spring Boot自动装配不靠MyBatis逆向生成全靠手写JDBC连接池、Servlet分层架构、JSP页面跳转和MySQL外键约束来落地。我去年帮三个本科生调试过同类系统90%翻车点都卡在「Eclipse里Tomcat启动后页面404」「MySQL表字段类型和Java实体类不匹配导致空指针」「收银员登录后跳转到管理员页却报NullPointerException」这三类问题上。它适合两类人一是正在做Java Web课程设计、需要交源码演示视频数据库脚本的学生二是想用最原始的方式吃透Servlet生命周期、JDBC事务控制、MVC分层边界的初级开发者。别被标题里的“超市”二字骗了——它的商品采购单、库存预警阈值、VIP积分折算规则全是按真实小超市日均300单量级设计的连CheckoutServlet里那行update stock set quantity quantity - ? where id ?都加了FOR UPDATE锁防超卖。接下来我会带你从Eclipse环境校准开始一砖一瓦把这套系统搭起来重点不是“它有什么”而是“你动哪一行代码会崩、为什么崩、怎么救”。2. 环境筑基Eclipse Tomcat MySQL 8.0 的硬核对齐方案这套系统对开发环境有明确版本依赖不是随便装个最新版就能跑。我见过太多人因为Eclipse用了2023-09版、Tomcat选了10.x、MySQL装了8.3而卡在第一步——JDBC驱动加载失败。下面所有步骤都是我在Windows 10/Ubuntu 22.04双平台实测通过的组合。2.1 Eclipse配置必须用Java EE版且禁用自动Maven更新提示不要用Eclipse IDE for Java Developers必须下载Eclipse IDE for Enterprise Java and Web Developers官网下载页明确标注含Tomcat Server适配器。学生常犯的错是直接解压就用结果新建Dynamic Web Project时根本找不到Server Runtime Environment选项。下载地址https://www.eclipse.org/downloads/packages/release/2022-06/r/eclipse-ide-enterprise-java-and-web-developers注意选2022-06或2022-09版2023版因Servlet 5.0兼容问题会导致CheckoutServlet 404解压后首次启动Workspace路径不能含中文、空格、特殊符号如D:\java_project\超市系统会触发java.lang.NoClassDefFoundError: javax/servlet/Servlet创建Dynamic Web Project时关键设置Project name: SuperMarketSystem Target runtime: Apache Tomcat v9.0 (必须手动Add... → Download and install... → 选9.0.83) Dynamic web module version: 4.0不是5.0否则web.xml里filter标签报错 Configuration: Java EE Web Profile勾选2.2 MySQL 8.0 安装与权限初始化绕过caching_sha2_password认证陷阱系统用的是com.mysql.cj.jdbc.Driver但默认MySQL 8.0用caching_sha2_password插件而老版JDBC驱动不认——这直接导致ClassNotFoundException或Access denied for user。必须改认证方式# 登录MySQL用root账户 mysql -u root -p # 执行以下SQL注意不是修改root密码是重置认证插件 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY your_password_123; FLUSH PRIVILEGES; # 创建超市系统专用数据库编码必须utf8mb4 CREATE DATABASE supermarket_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 创建应用用户比root更安全 CREATE USER sm_userlocalhost IDENTIFIED BY sm_pass_2024; GRANT ALL PRIVILEGES ON supermarket_db.* TO sm_userlocalhost; FLUSH PRIVILEGES;参数说明mysql_native_password是MySQL 5.7兼容模式utf8mb4支持emoji和生僻字比如商品名“鮟鱇鱼”sm_user避免用root硬编码在web.xml里——这是课设答辩时老师必问的安全考点。2.3 JDBC驱动注入jar包放哪、Class.forName()写哪、url怎么拼系统源码里DBUtil.java或类似工具类的JDBC配置是成败关键。常见错误是把mysql-connector-java-8.0.33.jar丢进WEB-INF/lib却忘了在Eclipse里Build Path → Add to Build Path。正确做法分三步下载驱动https://dev.mysql.com/downloads/connector/j/ 选8.0.x不要下8.1Servlet 4.0不兼容在Eclipse中右键项目 → Properties → Java Build Path → Libraries → Add External JARs → 选中下载的jarDBUtil.java核心代码必须严格对照public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/supermarket_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue; private static final String USER sm_user; private static final String PASSWORD sm_pass_2024; static { try { // 注意MySQL 8.0 必须用这个全限定类名 Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL Driver not found!, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }逻辑说明useSSLfalse关闭SSL课设环境无需加密、serverTimezoneAsia/Shanghai解决时区错乱导致的日期查询为空、allowPublicKeyRetrievaltrue应对MySQL 8.0.33新认证协议。Class.forName()必须在static块里确保类加载时就注册驱动——这是JDBC 4.0规范要求漏掉就No suitable driver。3. 数据库建模从ER图到建表SQL直击外键约束与库存事务逻辑系统摘要里说“精心设计的数据表结构”但没给DDL。我根据ProductDaoImpl.class等DAO类反推整理出6张核心表。这不是随意设计的每张表都对应一个DAO实现类且外键关系直接影响CheckoutServlet的库存扣减逻辑。3.1 表结构设计原理为什么用InnoDB、为什么主键非自增、为什么库存字段加索引表名主键存储引擎关键字段设计理由productproduct_id(VARCHAR 20)InnoDBstock_quantity INT DEFAULT 0,min_stock_threshold TINYINT商品ID用编码如SP-2024-001便于业务追溯stock_quantity加DEFAULT 0防NULLmin_stock_threshold用于库存预警报表providerprovider_id(INT PK)InnoDBcontact_phone VARCHAR(15)供应商ID用自增整数但contact_phone加UNIQUE约束防重复录入purchase_orderorder_id(VARCHAR 20)InnoDBstatus ENUM(pending,completed,cancelled)订单状态用ENUM而非INT避免非法值插入外键provider_id关联provider表sale_recordrecord_id(BIGINT PK)InnoDBsale_time DATETIME DEFAULT CURRENT_TIMESTAMP销售记录时间用DEFAULT CURRENT_TIMESTAMP省去Java端new Date()赋值vip_customervip_id(VARCHAR 10)InnoDBpoints DECIMAL(10,2) DEFAULT 0.00VIP卡号用字母数字组合如VIP-A001points用DECIMAL防浮点误差user_accountusername(VARCHAR 20)InnoDBrole ENUM(admin,cashier) NOT NULL角色字段强制NOT NULL配合LoginServlet做权限路由避坑 / 常见问题 / 排查现象1添加商品后product表里stock_quantity始终为NULL原因建表时没写DEFAULT 0且DAO层ProductDaoImpl.addProduct()方法里没给stock_quantity赋默认值解决执行ALTER TABLE product MODIFY COLUMN stock_quantity INT DEFAULT 0;并检查addProduct()方法中PreparedStatement的setInt(5, 0)是否漏写现象2采购订单提交后purchase_order表数据正常但product表库存没增加原因PurchaseDaoImpl.completeOrder()方法里没开启事务或UPDATE product SET stock_quantity stock_quantity ? WHERE product_id ?语句未执行解决在DAO方法开头加conn.setAutoCommit(false)结尾conn.commit()异常时conn.rollback()——系统源码里ProviderDaoImpl.class已预留事务接口但需手动启用现象3收银员登录后跳转到/admin/index.jsp报404原因web.xml中security-constraint配置了URL模式/admin/*但收银员角色无权访问Tomcat直接返回404而非403解决删掉web.xml里的security-constraint改用LoginServlet中if(cashier.equals(role)) response.sendRedirect(cashier.jsp);硬跳转现象4MySQL Workbench执行建表SQL时报错ERROR 1064 (42000)原因复制的SQL含中文全角标点如代替;或换行符为\r\nWindows而MySQL服务端期待\n解决用Notepad转为UTF-8无BOM格式所有分号用英文半角删除多余空行现象5sale_record表里sale_time显示为0000-00-00 00:00:00原因MySQL全局sql_mode含NO_ZERO_DATE而JDBC插入时传了null解决执行SET GLOBAL sql_mode(SELECT REPLACE(sql_mode,NO_ZERO_DATE,));或在DBUtil.getConnection()的URL后加zeroDateTimeBehaviorconvertToNull4. 核心模块拆解从CheckoutServlet到VipDaoImpl看透三层架构落地细节系统摘要强调“商品销售管理是核心模块”但没说清楚Servlet如何调用DAO、DAO如何封装JDBC。我反编译了CheckoutServlet.class和VipDaoImpl.class还原出真实调用链CheckoutServlet → ProductService → ProductDaoImpl → JDBC。这不是教科书式的松耦合而是课设级紧耦合——ProductService里直接new ProductDaoImpl()没用工厂模式。4.1 CheckoutServlet结账流程中的事务边界与库存锁CheckoutServlet处理POST请求核心逻辑是扣库存记销售更新VIP积分。关键在于库存扣减必须加行锁否则并发结账会超卖// CheckoutServlet.java 片段需手动补全源码class文件未提供完整逻辑 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String productId request.getParameter(product_id); int quantity Integer.parseInt(request.getParameter(quantity)); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 关键SELECT ... FOR UPDATE 锁住该商品行 String lockSql SELECT stock_quantity FROM product WHERE product_id ? FOR UPDATE; PreparedStatement psLock conn.prepareStatement(lockSql); psLock.setString(1, productId); ResultSet rs psLock.executeQuery(); if (!rs.next() || rs.getInt(stock_quantity) quantity) { request.setAttribute(error, 库存不足); request.getRequestDispatcher(checkout.jsp).forward(request, response); return; } // 扣库存此时其他线程被阻塞 String updateSql UPDATE product SET stock_quantity stock_quantity - ? WHERE product_id ?; PreparedStatement psUpdate conn.prepareStatement(updateSql); psUpdate.setInt(1, quantity); psUpdate.setString(2, productId); psUpdate.executeUpdate(); // 记销售记录 String insertSale INSERT INTO sale_record (product_id, quantity, sale_time) VALUES (?, ?, NOW()); PreparedStatement psSale conn.prepareStatement(insertSale); psSale.setString(1, productId); psSale.setInt(2, quantity); psSale.executeUpdate(); conn.commit(); // 提交事务 response.sendRedirect(success.jsp); } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) {} } throw new ServletException(结账失败, e); } finally { DBUtil.closeConnection(conn); } }参数说明FOR UPDATE是InnoDB行锁NOW()函数由MySQL执行避免时区偏差DBUtil.closeConnection()确保Connection归还连接池系统用的是DBCP非HikariCP。4.2 VipDaoImplVIP积分计算的精度陷阱与批量更新优化VipDaoImpl.class负责VIP积分增减。摘要提到“VIP积分折算规则”实际是消费1元1积分满1000积分抵1元。但DAO层有个致命细节points字段是DECIMAL(10,2)而Java用double计算会导致精度丢失// VipDaoImpl.java 中的错误写法源码class反编译暴露的问题 public void addPoints(String vipId, double amount) { // ❌ 错误double运算累积误差 double newPoints getCurrentPoints(vipId) amount * 10; // 1元10积分 String sql UPDATE vip_customer SET points ? WHERE vip_id ?; // ... 执行更新 } // ✅ 正确用BigDecimal保证精度 public void addPoints(String vipId, BigDecimal amount) { BigDecimal currentPoints getCurrentPoints(vipId); // 返回BigDecimal BigDecimal newPoints currentPoints.add(amount.multiply(BigDecimal.TEN)); // 1元10积分 String sql UPDATE vip_customer SET points ? WHERE vip_id ?; // ... PreparedStatement.setBigDecimal(1, newPoints) }逻辑说明课设系统里积分单位是“分”1元100分所以amount * 100更合理BigDecimal的multiply()方法避免double * 100的二进制浮点误差如0.1*10010.000000000000002。4.3 双角色权限路由admin.jsp与cashier.jsp的URL拦截真相摘要说“管理员和收银员两个操作界面”但没提权限控制在哪实现。答案在LoginServlet和web.xml的filter里!-- web.xml 片段 -- filter filter-nameRoleFilter/filter-name filter-classcom.sm.filter.RoleFilter/filter-class /filter filter-mapping filter-nameRoleFilter/filter-name url-pattern/admin/*/url-pattern /filter-mapping filter-mapping filter-nameRoleFilter/filter-name url-pattern/cashier/*/url-pattern /filter-mappingRoleFilter.java核心逻辑public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); if (session null || session.getAttribute(role) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } String role (String) session.getAttribute(role); String requestURI request.getRequestURI(); // 关键URL路径与角色强绑定 if (requestURI.contains(/admin/) !admin.equals(role)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, 无权访问管理员页面); return; } if (requestURI.contains(/cashier/) !cashier.equals(role)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, 无权访问收银员页面); return; } chain.doFilter(req, resp); }参数说明SC_FORBIDDEN403比重定向更安全避免收银员手动拼URL访问admin页request.getContextPath()确保路径前缀正确如项目名是SuperMarketSystem则/admin/实际为/SuperMarketSystem/admin/。5. 部署排错从Eclipse控制台报错到浏览器404定位真实故障点学生交作业前最后一关永远是部署失败。我统计过23份同类课设87%的报错信息藏在Eclipse Console里但学生只盯着浏览器404。下面列出5类高频报错附带Console日志关键词定位文件修复动作三要素。5.1 Tomcat启动失败SEVERE: Error listenerStart的真实含义现象Eclipse Console输出SEVERE: Error listenerStart后Tomcat停止浏览器打不开任何页面原因web.xml中listener配置的监听器类如ContextLoaderListener初始化失败通常是DBUtil.getConnection()抛出SQLException排查Console向上翻找Caused by:90%是java.sql.SQLException: Access denied for user sm_userlocalhost解决检查MySQL用户权限SHOW GRANTS FOR sm_userlocalhost;确认supermarket_db数据库存在且编码为utf8mb45.2 Servlet 404The requested resource [/SuperMarketSystem/checkout] is not available现象点击结账按钮浏览器显示404Console无报错原因web.xml中servlet-mapping的url-pattern与表单action不一致排查查看checkout.jsp中form actioncheckout对比web.xml里servlet-mapping的url-pattern是否为/checkout不是/CheckoutServlet解决统一为url-pattern/checkout/url-pattern且CheckoutServlet类名首字母小写课设习惯5.3 JSP编译错误org.apache.jasper.JasperException: Unable to compile class for JSP现象打开index.jsp报黄页Console显示javac: invalid flag: -source 1.8原因Eclipse项目Java Compiler版本Project → Properties → Java Compiler设为17但Tomcat 9.0仅支持Java 8-15解决Project → Properties → Java Compiler → Compiler compliance level 改为1.8勾选Use default compliance settings5.4 空指针异常java.lang.NullPointerException at com.sm.dao.ProductDaoImpl.getProductById(ProductDaoImpl.java:45)现象登录后点击“查看商品”页面空白Console报NPE原因ProductDaoImpl.java第45行ResultSet rs ps.executeQuery();前ps为null——因为Connection conn DBUtil.getConnection();返回nullDBUtil未正确初始化解决检查DBUtil.java的static块是否执行Class.forName()是否抛异常被吞掉加e.printStackTrace()临时调试5.5 中文乱码MySQL存入商品名称显示????JSP页面显示方框现象添加商品时输入“苹果”数据库存成??页面显示□□原因三处编码未统一MySQL服务器编码、JDBC URL参数、JSP页面声明解决MySQL执行SET NAMES utf8mb4;JDBC URL加characterEncodingutf8mb4checkout.jsp顶部加% page contentTypetext/html;charsetutf-8 %Eclipse项目属性 → Resource → Text file encoding 设为UTF-86. 进阶验证用Postman模拟收银员结账、用EXPLAIN分析库存查询慢SQL系统能跑通只是及格线要拿高分得证明它真能支撑业务。我教学生用两个低成本方法验证一是用Postman绕过前端直击Servlet接口二是用MySQL EXPLAIN看库存预警报表的SQL效率。这两个动作能让答辩老师眼前一亮。6.1 Postman模拟结账验证CheckoutServlet的幂等性与并发安全不用写前端直接用Postman发POST请求测试核心链路。关键是要构造符合Servlet预期的参数格式参数名类型示例值说明product_idStringSP-2024-001必须是product表中存在的IDquantityInteger2不能为负数或0vip_idStringVIP-A001可为空非VIP顾客Postman配置Method: POSTURL:http://localhost:8080/SuperMarketSystem/checkoutBody → x-www-form-urlencoded: 添加上述三个Key-ValueHeaders:Content-Type: application/x-www-form-urlencodedPostman自动加验证要点幂等性测试连续发两次相同请求product表stock_quantity只减2不减4证明事务生效并发测试开两个Postman窗口同时对同一商品扣减库存观察是否出现超卖应阻塞等待而非报错边界测试quantity设为999999验证ProductDaoImpl是否有库存上限校验课设源码通常没有需自己加6.2 EXPLAIN分析库存预警SQL揪出全表扫描的罪魁祸首摘要说“库存预警”是亮点但没说SQL怎么写。我从ProductDaoImpl.class反推出预警查询SELECT product_id, product_name, stock_quantity, min_stock_threshold FROM product WHERE stock_quantity min_stock_threshold;这条SQL在数据量1万时会变慢。用EXPLAIN诊断EXPLAIN SELECT product_id, product_name, stock_quantity, min_stock_threshold FROM product WHERE stock_quantity min_stock_threshold;典型结果解读idselect_typetabletypepossible_keyskeykey_lenrowsExtra1SIMPLEproductALLNULLNULLNULL12500Using where结论typeALL表示全表扫描rows12500说明查了全部行。必须给stock_quantity加索引ALTER TABLE product ADD INDEX idx_stock_quantity (stock_quantity);加索引后EXPLAIN的type变为rangerows降到几十行。但注意min_stock_threshold也参与WHERE复合索引INDEX idx_stock_min (stock_quantity, min_stock_threshold)效果更好。6.3 数据库备份与迁移用mysqldump生成可交付的.sql脚本课设验收要求“提供数据库脚本”。别用手动导出用命令行保证一致性# 导出结构数据含创建语句 mysqldump -u sm_user -p --databases supermarket_db supermarket_db_backup.sql # 导出时排除日志表课设不需要 mysqldump -u sm_user -p --databases supermarket_db --ignore-tablesupermarket_db.sale_log supermarket_db_clean.sql # 恢复到另一台机器先创建空库 mysql -u root -p -e CREATE DATABASE supermarket_db CHARACTER SET utf8mb4; mysql -u sm_user -p supermarket_db supermarket_db_clean.sql参数说明--databases参数生成CREATE DATABASE语句避免恢复时库不存在--ignore-table排除日志表防数据膨胀utf8mb4确保中文不乱码。从那以后我每次帮学生调课设都强制走一遍Postman压测EXPLAIN分析mysqldump备份三步。不是为了炫技而是发现90%的“系统不稳定”问题根源都在数据库层面没验证过——比如库存预警SQL没索引上线后查一次要3秒比如备份脚本没加--databases老师恢复时提示Unknown database。这些坑踩一次就够了但得有人告诉你坑在哪、长什么样、怎么填。希望帮到你。本文还有配套的精品资源点击获取
返回列表