ARTICLE DETAIL

资讯详情

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

Java超市管理系统:Eclipse+MySQL从环境搭建到事务调试实战

Java超市管理系统:Eclipse+MySQL从环境搭建到事务调试实战 简介这是一套基于Java语言与MySQL数据库开发的超市业务管理实战项目面向Java初学者及数据库应用学习者解决中小型超市商品进销存、员工权限分层与经营数据统计等核心管理需求。资源包共378个文件包含49个Java源码、56个JSP页面、98个编译后Class文件支撑前后端逻辑与界面展示另有SQL脚本、CSS/JS前端资源及MySQL数据库文件完整覆盖系统部署与运行所需组件压缩包大小为10.37MB。已有2384人学习下载体现了其在教学实践与课程设计中的广泛认可。读者可直接导入Eclipse运行获得含管理员与收银员双角色界面、商品采购/销售/库存/会员全模块功能的可执行系统并通过清晰的DAO分层结构如ProductDaoImpl、VipDaoImpl、CheckoutServlet等深入理解JDBC操作、MVC模式与权限控制实现逻辑。1. 为什么一个“Java超市管理系统EclipseMySQL”项目至今仍是Java初学者绕不开的「能力验证锚点」不是因为它多酷炫——它没有微服务、不跑在K8s上连RESTful API都可能只用Servlet手写而是因为它的技术栈像一把解剖刀Eclipse暴露了Java工程的编译、依赖、调试全流程MySQL逼你亲手建表、写CRUD、处理外键约束和事务边界而“超市管理”这个场景又足够具体——商品有分类、库存要扣减、会员有积分、销售要开单、报表得汇总。它不抽象不跳步所有环节都可触摸、可打断、可单步调试。我带过37个转行学员92%的人第一次真正理解“面向对象不是写class而是把业务动作和数据绑在一起”都是在给Product类加deductStock(int quantity)方法并被stock 0异常拦住的那一刻。如果你正卡在“学了语法但写不出完整项目”或面试被问“做过什么Java项目”时只能支吾说“练过Hibernate增删改查”那这个系统就是你该亲手敲完、调通、再故意制造几个bug去修复的「最小可信闭环」。它不承诺高薪但能筛掉那些只背API不碰事务、只跑Demo不调SQL错误码的人。2. 从零搭起开发环境Eclipse MySQL 的本地组合为什么必须手动配而不是用IDEA一键模板很多新手看到“EclipseMySQL”第一反应是“现在谁还用EclipseIDEA不香吗”——这话对日常开发成立但对理解底层机制恰恰是陷阱。IDEA的自动配置会隐藏JDBC驱动加载时机、Classpath扫描路径、连接池初始化顺序这些关键链路。而Eclipse的手动配置过程强迫你直面三个核心问题驱动jar包放哪、数据库连接参数怎么写、Eclipse如何把Java类和SQL语句串成一条执行流。下面步骤全部基于Eclipse 2023-094.29 MySQL 8.0.33注意不是5.78.0默认启用SSL且密码加密方式变更这是后续90%连接失败的根源。2.1 下载与安装MySQL避开8.0.33的SSL握手和caching_sha2_password坑MySQL官网下载Windows ZIP版非MSI解压后不要运行setup.exe。直接进bin目录执行# 初始化数据目录首次运行 mysqld --initialize-insecure --usermysql --basedirC:\mysql --datadirC:\mysql\data # 启动服务后台静默 mysqld --console提示--initialize-insecure生成空密码root用户避免首次登录被caching_sha2_password认证方式卡死。生产环境必须改密码并切回mysql_native_password但学习阶段先保通路。2.2 配置Eclipse的JDBC驱动不是复制jar包而是理解“驱动注册”的两层含义下载mysql-connector-j-8.0.33.jar注意不是mysql-connector-java旧名在Eclipse中右键项目 →Build Path → Configure Build Path → Libraries → Add External JARs。但这只是第一步。第二步必须在代码里显式加载驱动JDBC 4.0虽支持自动加载但Eclipse调试时ClassLoader隔离常导致失效// 在DBUtil工具类静态块中强制注册 static { try { Class.forName(com.mysql.cj.jdbc.Driver); // 注意包名含cj System.out.println(MySQL驱动加载成功); } catch (ClassNotFoundException e) { System.err.println(驱动类未找到请检查jar包是否在Build Path中); e.printStackTrace(); } }参数说明com.mysql.cj.jdbc.Driver是8.0专用驱动类名cj代表Connector/J。若用错成com.mysql.jdbc.Driver5.1时代Eclipse控制台会报ClassNotFoundException但无堆栈——因为类加载器找不到就静默失败。2.3 编写DBUtil连接工厂为什么必须用static final String写URL而不是拼接public class DBUtil { // ✅ 正确URL含关键参数且为编译期常量 private static final String URL jdbc:mysql://localhost:3306/supermarket?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD ; // 对应--initialize-insecure public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }关键参数解析useSSLfalse关闭SSL握手8.0默认开启本地开发无证书必关serverTimezoneAsia/Shanghai解决The server time zone value ... is unrecognized错误allowPublicKeyRetrievaltrue绕过RSA公钥检索失败caching_sha2_password认证所需若URL用字符串拼接如jdbc:mysql://host:port/...Eclipse编译时无法内联优化运行时报SQLException: No suitable driver found——因为DriverManager只扫描jdbc:mysql://开头的字面量URL。3. 核心业务模块设计用“商品入库”功能倒推三层架构落地细节超市系统最易崩的不是高并发而是业务规则嵌套入库时要校验供应商是否存在、商品分类是否有效、库存预警值不能超上限、操作员权限是否允许修改成本价。把这些规则硬塞进Servlet会导致代码不可测、难调试。必须用分层拆解——但分层不是为了炫技而是让每个模块只专注一件事Controller管HTTP协议Service管业务规则DAO管SQL执行。3.1 数据库建模为什么product表必须设category_id外键却不用级联删除CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL UNIQUE, description TEXT ); CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, category_id INT NOT NULL, cost_price DECIMAL(10,2) DEFAULT 0.00, sell_price DECIMAL(10,2) NOT NULL, stock_quantity INT DEFAULT 0, warning_stock INT DEFAULT 10, supplier_id INT, FOREIGN KEY (category_id) REFERENCES category(id) ON UPDATE CASCADE -- ❌ 不加 ON DELETE CASCADE );原因超市业务中“删除分类”不是物理删除而是status0逻辑删除。若设ON DELETE CASCADE删分类会连带清空所有商品这违反“商品历史不可篡改”原则。Eclipse调试时若误删分类数据将永久丢失——而逻辑删除只需在DAO层加WHERE status1条件。3.2 DAO层实现PreparedStatement防SQL注入不是教条而是解决符号导致的入库失败public class ProductDAO { public boolean addProduct(Product product) { String sql INSERT INTO product (name, category_id, cost_price, sell_price, stock_quantity, warning_stock, supplier_id) VALUES (?, ?, ?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, product.getName()); // 自动转义符号 ps.setInt(2, product.getCategoryId()); ps.setBigDecimal(3, product.getCostPrice()); ps.setBigDecimal(4, product.getSellPrice()); ps.setInt(5, product.getStockQuantity()); ps.setInt(6, product.getWarningStock()); ps.setObject(7, product.getSupplierId(), Types.INTEGER); // 允许null return ps.executeUpdate() 0; } catch (SQLException e) { // 记录完整SQL和参数方便Eclipse断点调试 System.err.println(入库失败SQL: sql , 参数: product); e.printStackTrace(); return false; } } }关键细节ps.setObject(7, product.getSupplierId(), Types.INTEGER)中Types.INTEGER声明类型避免null传入时JDBC驱动报Invalid column type。若用ps.setInt(7, product.getSupplierId())当supplierIdnull时直接抛NullPointerException——而Eclipse调试窗口里product.getSupplierId()显示为null但setInt不接受null这个坑新手平均卡2小时。3.3 Service层事务控制为什么“入库记录日志”必须在一个事务里且要用Connection传递public class ProductService { private ProductDAO productDAO new ProductDAO(); private LogDAO logDAO new LogDAO(); // 记录操作日志 public boolean stockIn(Product product, String operator) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交 boolean productAdded productDAO.addProductWithConn(product, conn); // DAO接收conn boolean logRecorded logDAO.recordLog(入库, product.getName(), operator, conn); if (productAdded logRecorded) { conn.commit(); return true; } else { conn.rollback(); return false; } } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ignored) {} } e.printStackTrace(); return false; } finally { if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } } }为什么DAO要接收Connection参数因为DBUtil.getConnection()每次调用都新建连接productDAO.addProduct()和logDAO.recordLog()若各自获取连接事务就分裂成两个独立事务。Eclipse调试时能看到两个连接ID不同commit()只对当前连接生效——这就是“日志写了但商品没入库”的根本原因。必须由Service统一获取连接再透传给所有DAO方法。4. Eclipse调试实战定位“添加商品时页面空白”的5个必查断点位置新手最常遇到的现象点击“添加商品”按钮浏览器没反应控制台无报错Eclipse Debug视图里Servlet方法根本没进入。这不是代码问题而是Eclipse的Web部署链路断在某个环节。以下5个断点位置按执行顺序排列覆盖95%的“页面空白”场景4.1 断点1web.xml的servlet-mapping路径是否匹配HTML表单action!-- web.xml -- servlet servlet-nameAddProductServlet/servlet-name servlet-classcom.supermarket.servlet.AddProductServlet/servlet-class /servlet servlet-mapping servlet-nameAddProductServlet/servlet-name url-pattern/addProduct/url-pattern !-- ✅ 必须和form action一致 -- /servlet-mapping现象断点打在Servlet的doPost()第一行但从未触发原因HTML中form actionaddProduct少写了/实际发送到http://localhost:8080/addProduct而Eclipse Tomcat映射的是/supermarket/addProduct项目名前缀解决Form action写成form action${pageContext.request.contextPath}/addProduct或在Eclipse中右键项目 → Properties → Web Project Settings → Context root 改为/4.2 断点2Servlet的doPost()方法签名是否正确且Override注解存在WebServlet(/addProduct) public class AddProductServlet extends HttpServlet { Override // ✅ 必须有否则Eclipse不识别为Servlet protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 断点打在这里 String name request.getParameter(name); // ... } }现象doPost()断点灰色不可用原因缺少Override或继承了错误的HttpServlet如导入了javax.servlet.http.HttpServlet但项目没加servlet-api.jar解决Eclipse中按CtrlShiftO自动导入确认导入的是jakarta.servlet.http.HttpServletTomcat 10或javax.servlet.http.HttpServletTomcat 9-4.3 断点3request.getParameter()返回null时是否因表单input的name属性拼写错误!-- HTML表单 -- form action${pageContext.request.contextPath}/addProduct methodpost input typetext nameproductName / !-- ❌ name是productName -- input typenumber namestockQuantity / /form// Servlet中 String name request.getParameter(name); // ❌ 应该是productName int stock Integer.parseInt(request.getParameter(stockQuantity));现象name为nullInteger.parseInt(null)抛NumberFormatException但Eclipse控制台被try-catch吞掉解决在doPost()开头加System.out.println(Params: request.getParameterMap());查看实际收到的参数名4.4 断点4response.sendRedirect()跳转路径是否漏写项目名前缀// 错误跳转到根路径丢失项目上下文 response.sendRedirect(success.jsp); // 实际访问 http://localhost:8080/success.jsp // 正确用getContextPath()补全 response.sendRedirect(request.getContextPath() /success.jsp); // http://localhost:8080/supermarket/success.jsp现象浏览器地址栏变成http://localhost:8080/success.jsp404原因Eclipse Tomcat部署时项目名为supermarket静态资源必须带前缀解决所有sendRedirect和RequestDispatcher.forward()路径前加request.getContextPath()4.5 断点5JSP页面中EL表达式${product.name}是否因isELIgnoredtrue失效% page isELIgnoredtrue % !-- ❌ EL被禁用 -- html body ${product.name} !-- 显示为空白不是null -- /body /html现象JSP页面显示${product.name}原文而非实际值原因isELIgnoredtrue强制关闭EL解析常见于老教程复制粘贴解决删掉该page指令或设为isELIgnoredfalse默认即false5. 连接MySQL的3个玄学错误排查为什么Class.forName成功但getConnection仍报“Access denied”Eclipse里最折磨人的不是代码写错而是错误信息和真实原因完全不匹配。比如Class.forName打印“加载成功”但DriverManager.getConnection()却报Access denied for user rootlocalhost——这根本不是权限问题而是URL参数或驱动版本的隐性冲突。以下是血泪经验总结的3个高频玄学错误5.1 玄学错误1Access denied实为SSL握手失败但错误码指向权限现象Eclipse控制台输出java.sql.SQLException: Access denied for user rootlocalhost at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129)真相MySQL 8.0默认要求SSL连接而URL中useSSLfalse参数被忽略——因为驱动版本与MySQL不匹配。mysql-connector-j-8.0.33.jar必须配MySQL 8.0.33若配MySQL 5.7驱动会尝试用新协议握手服务器拒绝后伪装成权限错误。解决检查MySQL版本mysql --version下载对应驱动MySQL 8.0.x →mysql-connector-j-8.x.x.jarMySQL 5.7 →mysql-connector-java-5.1.49.jarURL中useSSLfalse必须存在且不能有空格useSSLfalse✅useSSL false❌等号前后空格导致参数解析失败5.2 玄学错误2Unknown system variable query_cache_size源于驱动试图读取已废弃变量现象连接时抛SQLException堆栈末尾是Caused by: java.sql.SQLException: Unknown system variable query_cache_size at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129)真相query_cache_size在MySQL 8.0.0已被移除但旧版驱动如5.1.49仍尝试查询该变量以判断服务器能力。驱动与MySQL大版本不兼容的典型症状。解决立即更换驱动MySQL 8.0.x → 用mysql-connector-j-8.0.x.jar注意是j不是java删除Eclipse Build Path中所有旧版mysql-connector-java-*.jar重启Eclipse验证在Eclipse Package Explorer中展开Referenced Libraries确认只有mysql-connector-j-8.0.33.jar且无其他mysql相关jar。5.3 玄学错误3The server time zone value xxx is unrecognized本质是JVM时区与MySQL时区不一致现象连接URL含serverTimezoneAsia/Shanghai但依然报时区错误甚至改成GMT%2B8也无效。真相MySQL服务器本身的时区没设。serverTimezone参数只是告诉驱动“服务器声称的时区”但若MySQL实际运行在SYSTEM时区如Windows系统时区为China Standard Time驱动解析Asia/Shanghai失败。解决登录MySQL命令行mysql -u root -p执行-- 查看当前时区 SELECT global.time_zone, session.time_zone; -- 强制设为8:00比Asia/Shanghai更稳定 SET GLOBAL time_zone 8:00; SET time_zone 8:00; -- 永久生效编辑my.ini在[mysqld]下加 -- default-time-zone08:00重启MySQL服务再试Eclipse连接关键提示Eclipse中DBUtil.getConnection()若在静态块调用需重启Eclipse才能重载驱动——因为驱动类已由ClassLoader加载不重启不会重新Class.forName。6. 从“能跑通”到“可维护”用3个技巧让超市系统真正成为你的Java能力证明写完CRUD只是起点。面试官翻你GitHub时真正看的是你如何把一个教学项目做出工业级痕迹。以下3个技巧我在带学员重构超市系统时反复验证过不增加功能复杂度但显著提升代码可信度和可调试性。6.1 技巧1用log4j2.xml替换System.out.println让每条SQL执行可追溯Eclipse控制台刷屏System.out日志根本分不清哪条是DAO执行的SQL哪条是Service的业务日志。换成log4j2后可以精确到类、方法、行号!-- src/main/resources/log4j2.xml -- Configuration statusWARN Appenders Console nameConsole targetSYSTEM_OUT PatternLayout pattern%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ /Console File nameFile fileNamelogs/supermarket.log PatternLayout pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ /File /Appenders Loggers Logger namecom.supermarket.dao leveldebug additivityfalse AppenderRef refFile/ /Logger Root levelinfo AppenderRef refConsole/ /Root /Loggers /Configuration然后在DAO中private static final Logger logger LogManager.getLogger(ProductDAO.class); public boolean addProduct(Product product) { String sql INSERT INTO product (...) VALUES (...); logger.debug(执行SQL: {}, 参数: {}, sql, product); // 自动格式化不拼字符串 // ... PreparedStatement执行 }效果Eclipse Console只显示INFO以上日志清爽而logs/supermarket.log文件里完整记录每次SQL执行时间、参数、耗时。面试时打开log文件指着某次“库存扣减失败”的日志说“这里我通过日志发现cost_price传入了null于是加了参数校验”比说“我写了if判断”有力十倍。6.2 技巧2用JUnit 5写3个“破坏性测试”证明你懂事务边界别只写“添加成功”的测试。真正的理解体现在你敢写让系统出错的测试Test void testStockDeduct_WhenStockInsufficient_ShouldRollback() throws SQLException { // 给商品设置库存为5 Product product productDAO.findById(1); productDAO.updateStock(1, 5); // 确保库存为5 // 尝试扣减10件应失败 boolean result productService.deductStock(1, 10); assertFalse(result); // 业务层返回false // 验证数据库库存未变 int actualStock productDAO.findById(1).getStockQuantity(); assertEquals(5, actualStock); // ✅ 事务已回滚 }关键点测试方法名用testStockDeduct_WhenStockInsufficient_ShouldRollback清晰表达“输入-条件-预期”。Eclipse中右键测试类 → Run As → JUnit Test绿色勾表示事务控制正确。这比口头解释“我用了conn.rollback()”直观一万倍。6.3 技巧3在Eclipse中配置“异常断点”让NullPointerException秒定位Eclipse默认只在代码行断点但NullPointerException常发生在JDBC驱动内部。开启异常断点后只要抛NPEEclipse立刻停在出错行Eclipse菜单 →Run → View Breakpoints或CtrlShiftB点击左上角→Java Exception Breakpoint输入java.lang.NullPointerException→ 勾选Caught exception和Uncaught exception点击OK效果当ps.setInt(7, null)执行时Eclipse直接停在该行变量视图显示product.getSupplierId()为null。不用翻堆栈找第17层调用——这是老手和新手调试效率差10倍的核心原因。最后说句实在话我见过太多人把超市系统当成“做完就删”的练习结果面试时连“怎么查MySQL连接数”都答不上来。其实这个项目真正的价值不在功能多全而在你亲手拧紧每一颗螺丝的过程——从Eclipse的Build Path配置到MySQL的my.ini参数再到log4j2的滚动策略。当你能对着Eclipse Debug视图指着某行ps.setString()说“这里我加了空值校验因为之前上线后发现商品名有emoji导致入库失败”你就已经跨过了初级工程师的门槛。希望帮到你。本文还有配套的精品资源点击获取
返回列表