ARTICLE DETAIL

资讯详情

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

Java超市管理系统实战:JSP+Servlet+MySQL分层架构与部署避坑

Java超市管理系统实战:JSP+Servlet+MySQL分层架构与部署避坑 简介一款基于Eclipse与MySQL实现的Java超市管理系统面向正在做课程设计或毕业设计的Java学习者也适合希望了解零售业进销存流程与权限控制的开发人员。压缩包共378个文件、约10.37MB包含49个Java源文件、98个class编译文件与56个JSP页面另配SQL脚本、MySQL数据库文件、XML配置以及CSS、JS等前端资源目录结构按功能模块划分便于导入Eclipse后直接查看和运行。系统围绕商品销售、商品采购与商品数据统计三大模块展开提供管理员和收银员两种操作界面可覆盖商品增删改查、结账收银、供应商管理、采购订单跟踪以及销售排行、库存预警、毛利润分析等常用业务场景。目前已有2384人学习下载对需要搭建完整Java Web项目或学习Servlet、JSP与MySQL整合开发的读者来说这是一套代码结构完整、业务逻辑清晰的可运行参考。1. 为什么这套 Java 超市管理系统值得你动手跑一遍java超市管理系统EclipseMySQL是课设素材里被复跑次数最多的一类商品进销存完整闭环、管理员与收银员双角色页面、销售排行与库存预警报表恰好构成一个能讲清楚的 javaweb 项目完整案例。对准备 java 基础面试、期末赶课设、或者第一次想把 ServletJDBCJSP 串起来跑通的人来说它比零散的登录 demo 有价值得多——你能看到商品、会员、供应商、销售记录这几张业务表怎么设计也能看到收银台结账时库存如何联动扣减。适合三类人需要交课设的学生、想理解分层思想的 java 初学者、想拿真实业务场景练手的从业者。这套源码最实在的地方在于它不炫技每一行代码都在解决超市运营里的具体问题。2. 项目骨架与数据模型五个 DAO 类先把分层撑起来2.1 从 ProductDaoImpl 到 CheckoutServlet每个类负责哪一段业务拿到这套源码先别急着点运行去编译输出目录看一眼。里面躺着 ProductDaoImpl.class、VipDaoImpl.class、ProviderDaoImpl.class、UserDaoImpl.class 和 CheckoutServlet.class类名已经把架构交代了一半这是一个典型的 JSP Servlet DAO 三层结构。JSP 页面只负责展示Servlet 负责接收请求、调用业务逻辑DAO 负责用 JDBC 跟 MySQL 打交道。分层的好处是你可以单独改某一段而不影响其他层比如把数据库从 MySQL 换掉只需动 DAO 层页面一行不用改。五个类对应的职责很清晰ProductDaoImpl 管商品表的增删改查UserDaoImpl 管登录用户员工账号VipDaoImpl 管会员信息和折扣ProviderDaoImpl 管供应商档案CheckoutServlet 则负责收银台结账这个核心动作。注意 CheckoutServlet 是 servlet 而不是 dao它要同时调用商品和销售两张表的 DAO是典型的控制层逻辑编排。我一般会把这个对应关系写在 PPT 里答辩时直接讲“类名即职责”比背十页概念有效得多。下面是商品 DAO 里最典型的一个查询方法这套源码里的增删改都长这样public class ProductDaoImpl implements ProductDao { Override public Product findById(int id) { String sql SELECT id, name, price, stock, provider_id FROM product WHERE id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, id); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setName(rs.getString(name)); p.setPrice(rs.getBigDecimal(price)); p.setStock(rs.getInt(stock)); p.setProviderId(rs.getInt(provider_id)); return p; } } } catch (SQLException e) { e.printStackTrace(); } return null; } }代码里值得注意的有三处。第一用了 PreparedStatement 而不是 Statement占位符?先编译再传参能挡住最基础的 SQL 注入这是 java 面试里大概率被追问的点。第二价格字段用 rs.getBigDecimal 拿而不是 getDouble金额用浮点数在累加时会出精度尾巴后面统计报表一章会看到后果。第三try-with-resources 语法让 Connection、PreparedStatement、ResultSet 自动关闭这比你手写 finally 关连接干净得多。这套源码里每个 DAO 基本上保持了同样的模式一个接口定义方法一个 Impl 类写 JDBC 实现。接口存在的意义不是多敲几个字而是以后想换成 MyBatis 或 Spring JdbcTemplate 时业务层调的还是同一个接口这是分层设计的关键收益。2.2 MySQL 表结构商品、供应商、会员、销售记录怎么贴在一起数据库是整个系统的地基表没设计好后面所有代码都在打补丁。这套源码对应的建表语句大概是下面这几张核心表字段名和课设常规做法一致CREATE TABLE provider ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, contact VARCHAR(50), phone VARCHAR(20), address VARCHAR(200) ); CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, cost_price DECIMAL(10,2), stock INT NOT NULL DEFAULT 0, provider_id INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_product_provider FOREIGN KEY (provider_id) REFERENCES provider(id) ); CREATE TABLE vip ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, phone VARCHAR(20) UNIQUE, discount DECIMAL(3,2) DEFAULT 1.00, points INT DEFAULT 0 ); CREATE TABLE sale_record ( id INT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, product_name VARCHAR(100), count INT NOT NULL, unit_price DECIMAL(10,2), total DECIMAL(10,2), operator VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_product (product_id), KEY idx_create_time (create_time) ); CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(50) NOT NULL, role VARCHAR(10) NOT NULL DEFAULT cashier );重点说三个设计决策。第一金额字段全部用 DECIMAL(10,2)不是 FLOAT 也不是 DOUBLE浮点数二进制表示无法精确表达 0.1多行累计下来会差出一分钱这在零售系统里是不能忍的。第二sale_record 里冗余了 product_name 和 unit_price 两个字段这是刻意的历史销售记录不能跟着商品表走商品改价甚至删除后老订单仍然能还原出当时成交的单价和品名。第三role 字段直接存在 sys_user 表里admin 和 cashier 两个值决定了登录后进哪个界面这是整权限控制的最小实现简单但够用。外键这里提醒一句product 表通过 provider_id 关联 provider删除供应商前必须先清理该供应商下的商品否则 MySQL 会甩一个外键约束错误出来。常见做法是加一个删除前的引用检查或者在供应商表上加is_deleted逻辑删除标志不让物理删除发生。2.3 Eclipse 环境准备版本、驱动 jar、连接串一处都不能差这套源码的复现环境不算苛刻但版本没对齐会浪费时间。我一般建议JDK 8Tomcat 8.5MySQL 5.7 或 8.0Eclipse 必须用 Eclipse IDE for Enterprise Java 版本普通 Java 版没有 Servers 视图后面配 Tomcat 会非常别扭。MySQL 是开源免费的官网下载社区版装好把自带的 SQL 脚本导入即可导入命令参考下面# 用命令行导入建表脚本或直接用 Navicat 导入 mysql -uroot -p supermarket.sql # 验证表是否建成功 mysql -uroot -p -e USE supermarket; SHOW TABLES;代码层面需要确认两件事。第一把 mysql-connector-java.jar 加到项目的 Build Path 上右键项目 → Build Path → Configure Build Path → Add JARs这一步漏掉的话运行时报 ClassNotFoundException 是跑不掉的。第二检查 DBUtil 里的连接串MySQL 8.0 和 5.7 的连接串写法有差异// 适用于 MySQL 8.0注意驱动类名是 com.mysql.cj.jdbc.Driver String url jdbc:mysql://localhost:3306/supermarket?useSSLfalse characterEncodingutf8serverTimezoneAsia/Shanghai;连接串里三个参数缺一个就可能踩坑useSSLfalse 关掉 SSL 握手本地开发不需要加密链路characterEncodingutf8 保证中文明细不乱码serverTimezone 是 MySQL 8.0 的时区要求不写可能直接报错。驱动类方面5.7 用com.mysql.jdbc.Driver8.0 用com.mysql.cj.jdbc.Driver旧类名在 8.0 下只会打警告但还能跑别被警告带偏去改代码。环境装好后按 Eclipse 使用教程里的标准流程File → Import → Existing Projects into Workspace 选项目根目录然后在 Servers 视图新建 Tomcat 8.5 实例右键项目 Run As → Run on Server。不要用 Run As Java Application 去跑Web 项目必须有容器支撑这一步跑偏的话后面全乱。3. 核心业务拆解结账、采购入库与统计报表怎么联动3.1 CheckoutServlet收银台一次结账的完整动作超市管理系统最核心的页面是收银台而收银台背后真正干活的类就是 CheckoutServlet。它的完整动作链是这样的收银员提交购物车里的商品和数量Servlet 收到请求后先逐条查商品当前库存足够才继续如果有会员按会员折扣重新计算总价接着写销售记录、扣减库存最后返回一张结账成功的小票信息。这个过程里最怕的是做到一半出错比如库存刚扣成功但销售记录没写进去顾客走了账对不上。下面是按照常见课设做法还原的结账核心逻辑重点看参数解析和库存校验那段WebServlet(/checkout) public class CheckoutServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String[] productIds request.getParameterValues(productId); String[] counts request.getParameterValues(count); ProductDao productDao new ProductDaoImpl(); SaleDao saleDao new SaleDaoImpl(); // 会员折扣页面上带了 vipId 就按折扣算没有则按原价 double discount 1.0; String vipIdParam request.getParameter(vipId); if (vipIdParam ! null !vipIdParam.isEmpty()) { Vip vip new VipDaoImpl().findById(Integer.parseInt(vipIdParam)); if (vip ! null) { discount vip.getDiscount(); } } // 逐条校验库存并结账 try { for (int i 0; i productIds.length; i) { int pid Integer.parseInt(productIds[i]); int count Integer.parseInt(counts[i]); Product p productDao.findById(pid); if (p.getStock() count) { request.setAttribute(msg, 商品「 p.getName() 」库存不足); request.getRequestDispatcher(pos.jsp).forward(request, response); return; } double total p.getPrice().doubleValue() * count * discount; // 注意这里简化了事务实际应包在同一个 Connection 里 productDao.updateStock(pid, p.getStock() - count); saleDao.insert(new SaleRecord(pid, p.getName(), count, p.getPrice(), total, request.getSession().getAttribute(username))); } response.sendRedirect(checkoutSuccess.jsp); } catch (Exception e) { e.printStackTrace(); request.setAttribute(msg, 结账失败请重试); request.getRequestDispatcher(pos.jsp).forward(request, response); } } }这段代码要读明白的关键点有三个。第一个是参数用getParameterValues拿数组因为购物车是一次性提交多件商品页面上 checkbox 或隐藏域按这个 name 命名。第二个是库存校验必须在写库之前做而且校验和写库之间理论上还有并发窗口课设阶段这样做够用代码注释里也标了事务没包这是后面进阶章节要补的坑。第三个是接口上的WebServlet(/checkout)这是 Servlet 3.0 以后的注解写法不用再去 web.xml 里手动登记映射项目里如果看到 web.xml说明还保留了老式写法两种方式效果一样别重复配置。转发和重定向在这里有讲究结账失败要回填错误消息用 forward 把请求转发回 pos.jsprequest 里的 msg 属性还在结账成功用 sendRedirect防止用户按 F5 重复提交把同一笔订单再写一遍。这是 java 面试里常被问的 forward 与 redirect 区别在这个场景里体现得很具体。3.2 采购入库与库存联动进销存的“进”怎么落库采购模块是这套源码里最容易轻视、但数据联动最明显的一块。管理员在页面上选供应商、选商品、填采购数量提交后系统要做两件事往采购流水表里插一条记录同时把对应商品的库存数量累加上去。很多初版代码会把这两个动作做成先后两步先插流水再更新库存中间任何一步失败都会导致账实不符。正确的做法是把两步放进同一个事务或者至少在 SQL 层面保证第二步的原子性。库存累加最稳的写法是直接在当前值上做增量-- 采购入库库存累加避免先查后改 UPDATE product SET stock stock ? WHERE id ?; -- 采购流水 INSERT INTO purchase_record (provider_id, product_id, count, cost_price, operator) VALUES (?, ?, ?, ?, ?);为什么用stock stock ?而不是先 SELECT 再 UPDATE因为两个操作之间如果有另一个收银台同时结账扣库存你读到的旧值会把别人扣掉的库存加回来。直接在 SQL 里做增量运算数据库行锁会保证这一步只有一个事务在改这是解决并发库存错乱最基本的手段。理解了这个后面看数据库连接池和事务章节会更有感觉。3.3 数据统计销售排行、库存预警、毛利润报表数据统计是这个系统的亮点摘要里提到的销售排行、库存预警、毛利润分析本质上都是对 sale_record 和 product 两张表做分组聚合。这三条 SQL 是整套统计模块的地基值得反复看-- 1. 销售排行按商品分组求和取销量前 10 SELECT product_id, product_name, SUM(count) AS total_count FROM sale_record GROUP BY product_id, product_name ORDER BY total_count DESC LIMIT 10; -- 2. 库存预警低于阈值直接列出来 SELECT name, stock FROM product WHERE stock 20; -- 3. 毛利润零售总额 - 按进价计算的成本 SELECT p.name, SUM(s.total) AS sales_amount, SUM(s.count * p.cost_price) AS cost_amount, SUM(s.total - s.count * p.cost_price) AS gross_profit FROM sale_record s JOIN product p ON s.product_id p.id GROUP BY p.id ORDER BY gross_profit DESC;销售排行这条GROUP BY 里同时放了 product_id 和 product_name是因为 sale_record 里冗余存了 product_name按两个字段分组不会互相干扰。LIMIT 10 只是一个演示值页面上一般会做成可选的排名条数或者在 JSP 里接收一个 limit 参数。毛利润这条藏着一个常见的翻车点如果商品表里没有 cost_price 字段或者这个字段全是 NULL毛利润算出来就是空的。很多课设源码只存零售价不存进价统计模块的毛利润分析就成了摆设。我一般会强烈建议保留 cost_price哪怕录入时麻烦一点报表的价值会完全不一样。库存预警的阈值 20 是硬编码的示例真要上线应该做成配置项或者按商品类目区分收银员和管理员看到的是同一个数字但只有管理员能改阈值。三条 SQL 都有一个共同的边界问题跨月统计。正确写法是用半开区间WHERE create_time 2024-01-01 AND create_time 2024-02-01而不是BETWEEN 2024-01-01 AND 2024-01-31。BETWEEN 会漏掉 1 月 31 日 23 点之后的记录因为 DATETIME 精度到秒这个坑实测踩过报表差一天的数据在月结时非常难看。4. 双角色权限落地管理员与收银员的隔离边界在哪4.1 登录校验与 Session 里的角色标记这套系统的权限模型是“管理员页面 收银员页面”双界面底层靠的是登录时把角色写进 Session。登录 Servlet 拿到用户名密码后去 sys_user 表查查到了就把整个 user 对象和 role 字段分别放进 Session然后按角色跳转不同首页。代码大概是这样的结构String username request.getParameter(username); String password request.getParameter(password); User user new UserDaoImpl().findByUsernameAndPassword(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); session.setAttribute(role, user.getRole()); // admin 或 cashier if (admin.equals(user.getRole())) { response.sendRedirect(admin/index.jsp); } else { response.sendRedirect(cashier/index.jsp); } } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); }把 role 单独存一份而不是每次从 loginUser 里取是图个方便JSP 页面用${sessionScope.role}直接判空就行不用解对象。这里顺带提一句课设源码里密码大多是明文存库你心里要清楚这是隐患至少也该用 MD5 加盐。面试时如果被问到答“我知道这里能改进”比假装不存在好得多。Session 角色只是第一道门真正兜底的是 Filter。直接访问受保护页面的 URL 是绕过登录的常见路径必须有一个过滤器统一拦WebFilter(/*) public class LoginFilter implements Filter { 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); String uri request.getRequestURI(); if (uri.endsWith(login.jsp) || uri.endsWith(/login) || (session ! null session.getAttribute(role) ! null)) { chain.doFilter(req, resp); } else { response.sendRedirect(login.jsp); } } }过滤器的逻辑只有三条登录页和登录接口放行Session 里有角色的放行其余一律踢回登录页。注意request.getSession(false)带了参数没有 Session 时返回 null 而不是新建避免给每个请求都强行造一个会话。这种白名单式的过滤器是 Servlet 权限控制的标准兜底也是面试里“你项目权限怎么做”的标准答案。4.2 页面菜单按角色渲染收银员看不到的东西别让它出现双界面的体验层面靠 JSP 条件渲染实现。菜单条用 JSTL 标签按 role 判断收银员登录后看到的菜单里压根就没有供应商管理、采购入库和销售统计这几个入口管理员登录则能看到全部。菜单是这样处理的c:if test${sessionScope.role admin} lia hrefprovider/list.jsp供应商管理/a/li lia hrefpurchase/list.jsp采购入库/a/li lia hrefstat/saleRank.jsp销售统计/a/li lia hrefuser/list.jsp员工管理/a/li /c:if c:if test${sessionScope.role cashier} lia hrefpos.jsp收银台/a/li /c:if页面隐藏只是体验隔离真正的数据隔离要下沉到 DAO。最典型的是进价字段收银员能看到商品零售价和库存但绝不能看到 cost_price。如果所有角色共用同一个findAll()方法返回值里带着进价前端不显示也只是“看不见”数据已经传到了浏览器。更稳妥的做法是给查询方法做角色区分收银员走的查询 SQL 压根不 select cost_price 字段数据层面的隔离才是真隔离。4.3 权限设计的三个常见误用这个系统双角色的权限矩阵在答辩时可以直接画出来讲功能、管理员、收银员三列一摆就非常清楚功能管理员收银员商品增删改与进价维护可以不可以收银结账可以可以采购入库与供应商管理可以不可以会员管理可以只读销售统计与报表可以不可以员工账号管理可以不可以围绕这个矩阵最常见的三个误用得重点避掉。第一个是在 JSP 里用c:if把管理员按钮藏掉就以为安全了直接敲 URL 照样能访问管理页必须靠 Servlet 入口和 Filter 兜底。第二个是权限判断散落在各个 Servlet 里每个方法开头都复制一遍if(!role.equals(admin))改需求时容易漏改应该统一收进 Filter 或者一个权限工具类。第三个是 session 过期后页面还在点按钮发 AJAX 请求返回的是登录页而不是数据前端拿到一堆 HTML 也不知道怎么处理稳妥做法是过期后前端判断响应类型重定向去登录页。权限这套东西课设评分时往往是个拔高点。把 4.1 的 Session 登录、4.2 的菜单渲染、加上这个权限矩阵讲明白比你多做五个页面更让老师记住。5. 部署避坑连接失败、乱码、ClassNotFound 的四个现场5.1 现场一eclipse 找不到或无法加载主类 org.apache.catalina.startup.Bootstrap现象是点 Run As → Run on Server 后页面白屏控制台直接报“找不到或无法加载主类 org.apache.catalina.startup.bootstrap”看起来像是 Tomcat 坏了。原因基本是两类一是导入项目后没把 Tomcat 运行环境关联到项目的 Build Pathservlet-api.jar 这些根本没进编译路径二是用了普通 Eclipse Java 版压根没有 Server Runtime 的配置入口。解决是在 Window → Preferences → Server → Runtime Environments 里手动 Add 一个 Tomcat 8.5指定 Tomcat 安装目录然后项目右键 Properties → Build Path → Libraries确认列表里有 Server Runtime [Tomcat 8.5]。最后打开 Servers 视图把项目 Add 到 Tomcat 实例的发布列表再重启一次。这套顺序走完Bootstrap 的报错基本消失。5.2 现场二MySQL SSL 连接错误与 Public Key Retrieval is not allowed现象是项目启动后一查数据库就报 SSLHandshakeException或者干脆报“Public Key Retrieval is not allowed for user”Log 里还夹着成片的 WARN提示 Establishing SSL connection 没开安全认证。原因是 MySQL 8.0 默认认证插件是 caching_sha2_password驱动侧的 JDBC 默认要求 SSL 加密和公钥检索本地开发的连接串没给够参数就握手失败。解决是连接串补齐三个参数一行搞定jdbc:mysql://localhost:3306/supermarket?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaiuseSSLfalse 表示本地链路不加密allowPublicKeyRetrievaltrue 允许驱动向服务端请求公钥serverTimezone 保证日期时间字段不出差。这是 java 项目连 MySQL 8.0 最典型的玄学报错九成都是连接串少参数不是代码逻辑问题。MySQL 5.7 不会有这个错所以换数据库版本时记得回查连接串。5.3 现场三error 2002 (HY000): Cant connect to local MySQL server through socket现象是 Linux 下命令行mysql -uroot -p都进不去报 error 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock项目里当然也连不上。原因十有八九是 MySQL 服务没启动或者装完以后服务挂了。连接串写的是 localhostJDBC 在 Linux 上优先走 unix socket服务不活着 socket 文件就不存在。解决先确认服务再谈别的systemctl status mysqld # 没起来则启动 systemctl start mysqld # 确认 3306 在监听 ss -lnt | grep 3306这个坑的排查顺序是固定的服务状态 → 端口监听 → 驱动 jar → 连接串 → SQL 权限。任何一步过了再走下一步不要上来就怀疑代码。很多新人把半小时耗在改连接串上最后发现 mysqld 根本没启动教训很直接。5.4 现场四页面、数据库、控制台来回中文乱码现象是页面上提交中文商品名存进数据库变成一串???或者数据库里好好的页面一查出来乱码。原因是编码链断裂。中文从 JSP 页面到数据库要过四道关JSP 文件本身的编码、Tomcat 接收请求的编码、JDBC 传输编码、MySQL 表字符集四者必须一致断一处就乱。解决按一条链从前往后排JSP 顶部写pageEncodingUTF-8文件另存为 UTF-8Servlet 入口先request.setCharacterEncoding(UTF-8)JDBC 连接串加characterEncodingutf8建表时明确CHARACTER SET utf8mb4。Tomcat 8.5 默认 URIEncoding 已经是 UTF-8老版本 Tomcat 需要在 server.xml 的 Connector 上加URIEncodingUTF-8。血泪经验建库时别偷懒不写字符集等数据都进去了再改字符集数据库里已存的中文可能已经不可逆。第一次建表就统一 utf8mb4后面能少掉一半乱码问题。5.5 一个固定排查顺序这四个现场其实可以压缩成一套动作先看 MySQL 服务起来没有、端口 3306 通不通再看驱动 jar 在不在 Build Path然后查连接串参数全不全接着确认 SQL 里表名库名对不对最后才是编码。顺序不要乱。服务、端口、驱动、连接串、SQL、编码六步按序走九成报错都能在十分钟内定位。踩坑踩多了你会发现大部分“环境问题”最终都落在连接串和 jar 这两处。6. 进阶改造连接池、事务与答辩前必过的验证清单6.1 用数据库连接池替换裸 JDBC裸 JDBC 每次 getConnection 都要新建物理连接频繁结账时性能会很差。最省事的改造是上 Druid 连接池原理是启动时预创建一批连接反复复用。改法很简单写一个 druid.properties 放进 src 目录然后替换 DBUtil 的实现urljdbc:mysql://localhost:3306/supermarket?useSSLfalseallowPublicKeyRetrievaltruecharacterEncodingutf8 usernameroot password123456 initialSize5 maxActive20 maxWait10000public class DBUtil { private static DataSource dataSource; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(druid.properties)) { Properties props new Properties(); props.load(in); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new RuntimeException(初始化连接池失败, e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }所有 DAO 的调用方只认 DBUtil.getConnection()所以改完这一处全项目无感切换。initialSize 是初始连接数maxActive 是上限maxWait 是拿不到连接时的等待毫秒数这三个参数在演示时拿出来说比一句“连接池更快”有说服力。6.2 把结账事务包进同一个 Connection3.1 里的结账代码注释写了“简化了事务”进阶就该把它补上。扣库存和写销售记录必须同生共死用 setAutoCommit(false) 手动控制Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); productDao.updateStockWithConn(conn, pid, count); // 扣库存 saleDao.insertWithConn(conn, record); // 写销售记录 conn.commit(); } catch (Exception e) { conn.rollback(); throw new RuntimeException(结账失败已回滚, e); } finally { conn.setAutoCommit(true); conn.close(); }注意原来的 DAO 方法内部自己开连接这里要改成把 conn 传进去的重载方法事务才能共享同一个连接。这是“事务收拢”的标准做法答到这一步评委就知道你不只是把代码跑通了。6.3 答辩前必过的验证清单检查项做法换库能跑另一台机器导入 SQL、改连接串全流程重走一遍金额精度结账多件商品确认无浮点尾差权限越权用收银员账号直接敲 admin 页面 URL应被拦回登录页库存边界结账数量超过库存时有提示且不写销售记录中文编码中文商品名、会员名、供应商名增删改查全部走一遍从那以后我每次接手 Java 课设源码都强制先按这张清单把全流程跑一遍再动代码——百分之八十的“玄学报错”最后都落在这五个固定点上跑通了才敢说这套源码是真正可用的。希望帮到你。本文还有配套的精品资源点击获取
返回列表