
简介基于Java的超市采购管理系统设计与实现资料以docx格式打包提供单个文件总体积约674KB。这份文档面向Java课程设计、毕业设计以及需要了解采购管理系统开发流程的开发者以人人乐超市为业务背景覆盖采购计划、供应商管理、库存控制、订单处理和数据分析等核心环节。内容按正式项目文档组织依次呈现系统分析、技术经济营运可行性分析、需求分析、数据流图、数据字典、概要设计等章节并附有系统关联图与业务流程说明可帮助读者理解从现状调研到系统架构设计的完整过程。目前已有306人学习适合作为项目开题参考、系统设计模板或论文写作素材。压缩包内仅含一个docx文件文件虽少但框架完整便于直接查阅和复用其中的建模思路。1. 这份「基于 Java 超市采购管理系统」设计文档从一个毕业设计到可复现的进销存骨架手头这份《基于 Java 超市采购管理系统设计与实现》是一个完整的毕业设计文档系统以 JSP JavaBean 为核心、SQL Server 2005 做数据存储采用 B/S 三层结构覆盖了客户信息管理、订单管理、商品信息管理、供应商管理、库存管理和系统用户管理六大模块。它和网上那些只贴几段代码的资源最大的区别在于从可行性分析、数据流图、数据字典、E-R 图一直到数据库表设计、界面设计和系统测试软件工程全流程都有。换句话说你拿到的不是一段代码而是「怎么把一个采购管理系统从需求梳理到建成」的完整方法论。适合正在做进销存方向毕设的人、想补系统设计文档的开发者以及需要从零搭一个超市进销存骨架然后往里填业务逻辑的从业者。照着它走一遍你对 JSP 项目里「表怎么建、Bean 怎么分、页面怎么串」会有一个很实在的认知而不是停留在背概念。2. 系统分析与技术选型B/S JSP/JavaBean 三层结构为什么够用2.1 可行性分析三条判断标准直接决定项目生死很多人在做同类系统时一上来就写代码等写到一半发现环境不支持、成本失控才回头补分析。这份文档给了一个很规范的处理顺序把可行性拆成技术、经济、营运三条线。技术可行性看的是现有条件能不能支撑开发。原文档给出的判断依据是公司内部有局域网各部门用的是 PⅢ 以上 PC容量和速度满足开发要求有专业的 IT 人员熟悉开发工具且有数据库开发经验。对应到你自己做项目就是要确认三件事机器能不能跑起开发工具和数据库团队有没有人写过 JDBC 或 JSP目标数据库版本是否能被当前驱动正常连接。技术可行性里还有一个容易被忽略的点——编程语言与开发工具选型。原文档明确用 JSP 技术、SQL Server 2005、MyEclipse 8.5 组合这在当年是 JSP 教学的标配放到今天也依然成立只是工具可以换成 IDEA Tomcat 8.5 SQL Server 2019。经济可行性主要算两笔账支出和收益。支出包括设备购置、软件开发、管理和维护、人员工资和培训费收益分成能用钱衡量的部分和难以量化的部分。原文档有一个很关键的判断——基于现有计算机及配套设备不需要添置硬件由内部人员自行开发能省下软件采购费用同时减少重复性书面报告降低办公费用。这条逻辑对独立开发者尤其适用单机开发环境 开源工具几乎零硬件成本。营运可行性容易被新手跳过但它是老师答辩最爱问的角度。原文档强调了两点管理层是否支持、员工经过短时间培训能否上手。放在真实场景里就是系统是否贴合现有采购流程库管员是否愿意录入数据采购单审批是否还要在线下走一遍。如果一个系统让用户每天多填三张表哪怕技术再先进也很难落地。2.2 B/S 三层结构浏览器、服务器、数据库各自该干什么原文档在 4.1.1 节重点介绍了 B/S 体系结构并对比了它相对 C/S 的优势。B/S 模式下客户端只需要一个浏览器应用逻辑集中在服务器端维护和升级都发生在服务器上客户端不需要任何改变。它的开放标准、跨平台性和低成本维护是选择它的直接理由。三层结构落到这个项目里分工非常清晰。第一层是浏览器端 JSP 页面只负责接收用户输入和展示数据不写复杂业务第二层是 JavaBean承担订单校验、库存判断、登录验证这些业务逻辑第三层是 SQL Server 数据库负责数据的持久化存储。这样做的好处是任何一层的改动不会直接影响另外两层。原文档举了一个很典型的例子购物车程序里实现 AddItem 方法如果后来要加一个「库存不足不能购买」的判断只需要改 JavaBean 里的 AddItem前台 JSP 完全不用动。这就是把业务逻辑从表示层分离出来的价值。如果你习惯了把 SQL 和业务判断都写在 JSP 里初期会觉得很省事但页面超过几百行之后维护成本会指数上升。JSP 里的 Java 代码片段越多页面越难读。JavaBean 这种组件模型的另一个好处是复用性编写并测试通过一个 Bean 后可以随处引用。对超市采购这个场景登录验证、库存变更、订单生成这几段逻辑是高频复用的写成独立 Bean 比在每个页面里复制粘贴要靠谱得多。2.3 业务流程与模块拆分从数据流图到六大功能模块业务流程分析是整个需求分析阶段的重头戏。原文档用系统流程图图例描述了供应商、门店、库房之间的单据流转重点是搞清「谁把什么数据交给谁经过什么处理存到哪里」。业务流程图中的符号类别包括系统内人员、系统外实体、单据报表、账目、处理、数据流向和存储。把它们画清楚系统边界就出来了。数据流图部分原文档先画了系统关联图把系统当做一个加工环节明确外部实体是客户部和供应商再自顶向下逐层扩展加工逻辑。这里有一个很实用的操作方法先把系统跟外部打交道的数据流列全比如供应商送货单、客户订单、库存预警报表再一层层细化到具体加工。我从这套流程里提炼出六个稳定的功能模块模块核心功能涉及的主要数据客户信息管理客户档案的增删改查客户编号、名称、联系方式订单管理采购订单与销售订单处理订单号、商品明细、交货时间商品信息管理商品档案维护商品编号、规格、价格供应商管理供应商资料与供货关系供应商编号、供货商品、结算方式库存管理入库、出库、库存查询库存量、入库批次、预警值系统用户管理用户权限与登录验证用户名、密码、角色模块拆分的原则是每个模块完成一组高内聚的功能模块之间通过数据传递而不是互相直接操作对方的表。比如库存模块必须提供「入库登记」的方法供订单模块调用而不是让订单模块直接写库存表。这份文档在概要设计阶段把这六个模块的调用关系和数据联系都做了结构化设计哪怕你不想照抄它的代码这套模块划分思路也值得直接搬到你自己的项目里。3. 数据库与模块设计把 E-R 图和五张核心表落到 SQL Server 20053.1 E-R 模型从实体关系到五张核心表数据库设计是这份文档最扎实的部分。原文档在 4.2 节给出了实体描述、联系描述和 E-R 图虽然论文里的 E-R 图是静态截图但实体和联系的关键信息都在。我按业务常识把实体关系还原一下并梳理清楚应当建立的主外键约束。首先是核心实体用户系统登录账户、供应商、商品、订单、库存。实体之间的联系有三条主链。第一条是供应商与商品之间的供应关系一个供应商可以供应多种商品一个商品也可以有多个供应商这是典型的多对多实际设计中会通过「供应关系表」拆开。第二条是订单与商品之间的包含关系一张订单包含多条商品明细商品明细需要记录数量、单价和金额。第三条是商品与库存之间的对应关系一个商品有一条对应的库存记录库存量随入库单和出库单变动。原文档在 3.3 节数据字典里以「订单」数据流为例给出了很规范的结构定义订单 订单号 日期 客户名称 产品名称 规格 数量 单价 付款方式 交货时间 交货地点流通量约 60 份每天高峰时段是上午 9 点到 11 点。这段话信息量很大它直接决定了订单表和订单明细表的字段边界。你不需要额外猜业务照着数据字典的条目去建表字段基本不会漏。3.2 核心表字段设计类型、长度与默认值根据实体关系和数据字典我把这个系统最核心的六张表整理成下面的字段清单。注意原文档里 SQL Server 2005 的语法在老版本上没问题但字段类型选择对今天的 MySQL 或 SQL Server 2019 同样适用。用户表sys_user字段名类型说明user_idint identity(1,1)主键自增user_namevarchar(20)登录名唯一passwordvarchar(32)密码建议存 MD5rolevarchar(10)角色管理员/采购员/库管员商品表product字段名类型说明product_idint identity(1,1)主键product_codevarchar(20)商品编码条码扫描用product_namevarchar(50)商品名称specvarchar(30)规格unitvarchar(10)单位如箱/瓶pricedecimal(10,2)采购单价stock_lowerint库存预警下限供应商表supplier字段名类型说明supplier_idint identity(1,1)主键supplier_namevarchar(50)供应商全称contactvarchar(20)联系人phonevarchar(20)联系电话addressvarchar(100)地址采购订单表purchase_order字段名类型说明order_idint identity(1,1)主键order_novarchar(20)订单编号业务可见supplier_idint外键关联供应商order_datedatetime下单日期total_amountdecimal(12,2)订单总金额statusint0 草稿 / 1 已审核 / 2 已入库采购订单明细表purchase_order_detail字段名类型说明detail_idint identity(1,1)主键order_idint外键关联采购订单product_idint外键关联商品quantityint采购数量pricedecimal(10,2)行项目单价库存表stock字段名类型说明stock_idint identity(1,1)主键product_idint外键关联商品quantityint当前库存量update_timedatetime最后变动时间设计时有一个原则值得强调金额和单价一律用 decimal不用 float 或 money。float 是浮点数二进制无法精确表示 0.1累加会出现精度错乱money 类型在 SQL Server 里虽然存在但不如 decimal 直观跨库迁移也麻烦。数量用 int日期用 datetime这些选择最终会影响报表统计的准确性。3.3 建表脚本照抄就能跑通的 SQL Server 2005 DDL原文档在第 5 章给出了数据库的逻辑设计和实现说明但没有提供完整可执行的建表脚本。下面这段脚本按上述表结构整理主键、外键、默认值都带上在 SQL Server 2005 及以上版本可以直接执行。CREATE DATABASE SupermarketPurchase; GO USE SupermarketPurchase; GO CREATE TABLE sys_user ( user_id INT IDENTITY(1,1) PRIMARY KEY, user_name VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(32) NOT NULL, role VARCHAR(10) NOT NULL DEFAULT storekeeper ); CREATE TABLE supplier ( supplier_id INT IDENTITY(1,1) PRIMARY KEY, supplier_name VARCHAR(50) NOT NULL, contact VARCHAR(20), phone VARCHAR(20), address VARCHAR(100) ); CREATE TABLE product ( product_id INT IDENTITY(1,1) PRIMARY KEY, product_code VARCHAR(20) NOT NULL, product_name VARCHAR(50) NOT NULL, spec VARCHAR(30), unit VARCHAR(10), price DECIMAL(10,2) NOT NULL DEFAULT 0, stock_lower INT NOT NULL DEFAULT 10 ); CREATE TABLE purchase_order ( order_id INT IDENTITY(1,1) PRIMARY KEY, order_no VARCHAR(20) NOT NULL, supplier_id INT NOT NULL REFERENCES supplier(supplier_id), order_date DATETIME NOT NULL DEFAULT GETDATE(), total_amount DECIMAL(12,2) NOT NULL DEFAULT 0, status INT NOT NULL DEFAULT 0 ); CREATE TABLE purchase_order_detail ( detail_id INT IDENTITY(1,1) PRIMARY KEY, order_id INT NOT NULL REFERENCES purchase_order(order_id), product_id INT NOT NULL REFERENCES product(product_id), quantity INT NOT NULL DEFAULT 0, price DECIMAL(10,2) NOT NULL DEFAULT 0 ); CREATE TABLE stock ( stock_id INT IDENTITY(1,1) PRIMARY KEY, product_id INT NOT NULL UNIQUE REFERENCES product(product_id), quantity INT NOT NULL DEFAULT 0, update_time DATETIME NOT NULL DEFAULT GETDATE() );提示product_id 在 stock 表里设置了 UNIQUE表示一个商品只对应一条库存汇总记录。这样做的好处是库存查询不需要对多条记录求和坏处是每次入库要先判断是否已有记录逻辑上需要 INSERT 或 UPDATE 分支。如果要做批次管理这里应该改成一对多结构加一个批次号和入库时间字段。脚本里的 identity(1,1) 是 SQL Server 的自增语法等价于 MySQL 的 AUTO_INCREMENT。外键用 REFERENCES 直接声明好处是数据库层能强制约束例如删除一个已被订单引用的供应商时SQL Server 会报错而不是让 Java 代码去处理孤儿数据。对于这个量级的进销存系统用数据库约束比全靠程序保证要省心。4. 从登录到库存入库JSP JavaBean 的典型实现路径4.1 JavaBean 负责逻辑、JSP 只做展示分层理由与调用关系原文档在 4.1.1 节花了较大篇幅讲 JavaBean核心观点是业务逻辑和前端表示必须分离。所有 JSP 代码应限制在表示层业务逻辑交给 JavaBeanJavaBean 把数据返回给 JSP 页面页面只负责格式化并输出。如果你用过把 SQL 和业务判断全写在 ASP 页面里的老项目一定体会过那种改一行逻辑要翻几百行代码的痛苦。这个项目里JavaBean 大致分成两类。一类是实体类对应数据库表比如 User、Product、Stock属性名跟表字段对齐主要是为了传值。另一类是业务类比如 UserService、StockService里面写登录验证、库存更新、订单生成这些操作。JSP 页面通过 jsp:useBean 或直接 new 一个 Bean 来调用。因为服务器端 Java 代码对 JSP 页面是透明的页面只看到返回的结果对象所以后续加权限、加日志都不需要动页面的展示结构。JSP 与 JavaBean 的调用链路通常是浏览器提交表单 → JSP 接收参数 → 创建或获取业务 Bean → Bean 内部建立数据库连接并执行 SQL → 把结果返回给 JSP → JSP 用标签或表达式输出。这段链路的每一环在下面几个小节里都有对应代码。4.2 登录模块从表单到 Connection 验证的完整链路登录是每个管理系统的第一道关卡代码量不大但能把 JSP 页面、JavaBean、JDBC 连接三者串起来。下面先给表单页面再给业务 Bean。userLogin.jsp 的核心代码form actionloginCheck.jsp methodpost input typetext nameuserName placeholder用户名 / input typepassword namepassword placeholder密码 / button typesubmit登录/button /formloginCheck.jsp 里调用业务 Bean% page importcom.purchase.bean.UserBean % % request.setCharacterEncoding(UTF-8); String userName request.getParameter(userName); String password request.getParameter(password); UserBean userBean new UserBean(); boolean ok userBean.login(userName, password); if (ok) { session.setAttribute(userName, userName); response.sendRedirect(main.jsp); } else { out.print(scriptalert(用户名或密码错误);history.back();/script); } %UserBean 中的 login 方法package com.purchase.bean; import java.sql.*; public class UserBean { private String driver com.microsoft.sqlserver.jdbc.SQLServerDriver; private String url jdbc:sqlserver://localhost:1433;DatabaseNameSupermarketPurchase; public boolean login(String userName, String password) { Connection conn null; PreparedStatement ps null; ResultSet rs null; try { Class.forName(driver); conn DriverManager.getConnection(url, sa, 123456); String sql SELECT COUNT(*) FROM sys_user WHERE user_name? AND password?; ps conn.prepareStatement(sql); ps.setString(1, userName); ps.setString(2, password); rs ps.executeQuery(); if (rs.next()) { return rs.getInt(1) 0; } } catch (Exception e) { e.printStackTrace(); } finally { try { if (rs ! null) rs.close(); } catch (Exception e) {} try { if (ps ! null) ps.close(); } catch (Exception e) {} try { if (conn ! null) conn.close(); } catch (Exception e) {} } return false; } }这段代码里有几个细节值得说。第一所有 JDBC 访问都使用 PreparedStatement参数通过 setString 绑定避免 SQL 注入。第二finally 里按 ResultSet → Statement → Connection 的顺序反向关闭资源如果倒过来关或者不关长时间运行会耗尽数据库连接数。第三密码字段在示例里直接明文比对真实系统应改为比对 MD5 值数据库里存的也应该是摘要而不是原文。第四数据库连接参数写死在类里方便演示但真实部署时建议读取配置文件不然换一台机器就要改代码重新编译。4.3 库存添加入库数量更新与传统 JDBC 事务库存添加是采购系统的核心操作它不只是往库存表插一条记录还要同步判断商品是否已存在、更新订单状态。如果不加事务会出现订单审核完成但库存没有增加的中间状态。下面这段代码展示一个标准的入库处理方法。public boolean doStockIn(int orderId, int productId, int quantity) { Connection conn null; PreparedStatement ps1 null; PreparedStatement ps2 null; PreparedStatement ps3 null; try { Class.forName(driver); conn DriverManager.getConnection(url, sa, 123456); conn.setAutoCommit(false); String updateStock UPDATE stock SET quantity quantity ?, update_time GETDATE() WHERE product_id ?; ps1 conn.prepareStatement(updateStock); ps1.setInt(1, quantity); ps1.setInt(2, productId); int rows ps1.executeUpdate(); if (rows 0) { String insertStock INSERT INTO stock(product_id, quantity) VALUES(?, ?); ps2 conn.prepareStatement(insertStock); ps2.setInt(1, productId); ps2.setInt(2, quantity); ps2.executeUpdate(); } // UPDATE 后没有记录则执行 INSERT两条路径必走其一 String updateOrder UPDATE purchase_order SET status2 WHERE order_id?; ps3 conn.prepareStatement(updateOrder); ps3.setInt(1, orderId); ps3.executeUpdate(); conn.commit(); return true; } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (Exception ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { try { if (ps1 ! null) ps1.close(); } catch (Exception e) {} try { if (ps2 ! null) ps2.close(); } catch (Exception e) {} try { if (ps3 ! null) ps3.close(); } catch (Exception e) {} try { if (conn ! null) conn.close(); } catch (Exception e) {} } }逻辑说明先尝试更新库存表如果影响行数为 0说明商品还没有库存记录改为 INSERT随后把采购订单状态更新为已入库两条 SQL 打包在一个事务里同时提交。任何一个环节抛异常回滚到最初状态。参数说明productId 和 quantity 由页面传入orderId 用于把订单状态从「已审核」推进到「已入库」。setAutoCommit(false) 是事务开始的控制开关commit 和 rollback 分别是成功确认和异常回滚。这个写法的前提是库存表只存汇总数量不做批次和保质期管理如果后续要加保质期这张表的结构和 SQL 都要重做所以设计时要想清楚边界。4.4 页面之间参数传递request 与 session 分工JSP 项目的参数传递比框架项目简单但容易用错。登录成功后userName 存入 session因为后续每个页面都要显示当前操作人订单查询的条件、分页页码这类临时参数应放在 request 里随请求生命周期结束而销毁提示信息如「添加成功」「库存不足」可以用 request 属性传回页面也可以直接在 JSP 里用 out.print 输出 alert。session 不能滥用存太多对象会占用服务器内存并可能引发并发访问时的状态混乱。表单提交方式也要区分。查询操作用 GET参数会暴露在 URL 上方便复制和刷新新增、修改、删除操作用 POST避免参数出现在地址栏也避免刷新页面时重复提交。库存添加这种写操作除了用 POST还应该在提交后重定向到一个查询页面防止用户按 F5 导致重复入库。熟悉这套 request 与 session 的边界之后往 Spring MVC 迁移时你会发现思路是通用的只是注解和框架把传参过程收得更简洁了。5. 避坑指南连接、乱码与表设计的五条踩坑记录5.1 ClassNotFoundException驱动加载失败现象运行项目时控制台报 ClassNotFoundExceptioncom.microsoft.sqlserver.jdbc.SQLServerDriver或者 java.lang.UnsupportedClassVersionError。原因SQL Server 2005 对应的微软官方驱动版本很老类名和现在的 mssql-jdbc 不完全一致。老教程里常用 com.microsoft.jdbc.sqlserver.SQLServerDriver这个类只在老版本的 sqljdbc.jar 里存在新版驱动已经改成 com.microsoft.sqlserver.jdbc.SQLServerDriver。还有一个常见情况是 jar 包没有放到 WEB-INF/lib 下Tomcat 运行时根本找不到。解决确认驱动 jar 包在 WEB-INF/lib 目录里直接用 SQL Server 2005 时可以用 sqljdbc.jar 或 jtds 驱动。jtds 的 URL 写法是 jdbc:jtds:sqlserver://localhost:1433;DatabaseNameSupermarketPurchase类名是 net.sourceforge.jtds.jdbc.Driver。如果你的机器装的是 SQL Server 2019建议直接用较新的 mssql-jdbc 驱动同时把数据库兼容级别调整到目标版本。5.2 中文乱码页面、请求、数据库三级编码不一致现象页面显示商品名称是「???」或者从前端传到数据库里的中文变成乱码但数据库客户端里直接插数据却正常。原因JSP 页面本身是 UTF-8但请求没有设置编码Tomcat 默认按 ISO-8859-1 解析 POST 参数再加上 SQL Server 数据库的排序规则如果是 Latin1_General也会把中文存成乱码。三级编码只要有一级不一致数据就会从源头坏掉。解决在接收参数的 JSP 第一行加 request.setCharacterEncoding(UTF-8)并且保证页面顶部 pageEncoding 也声明为 UTF-8。数据库层面建库时指定排序规则为 Chinese_PRC_CI_AS这是中文环境的标准排序规则。如果数据已经乱码了只能清掉重建没有后悔药所以建库时就把排序规则定好最省事。5.3 库存扣减没加事务出现负数或单边数据现象订单审核完成但库存没有增加或者并发点击时库存数量算错甚至扣成负数。原因库存更新和订单状态更新是两个 SQL 操作中间任何一个失败了另一个仍然会提交。我见过有人先更新订单状态再更新库存结果库存 SQL 报错订单显示已入库实际库存没变。解决把两个操作包在同一个事务里参考 4.3 节的写法setAutoCommit(false) 开始全部成功再 commit异常时 rollback。更稳妥的做法是把库存变更做成单独的库存流水表每笔入库出库都记一条明细库存表的值由流水汇总得出。这样做虽然查询要写 JOIN但对账时能看到每一笔变动不至于出问题后无从查起。5.4 money 与 decimal 的精度错乱现象报表里算出 199.999999 这种金额或者两个单价一样的商品合计金额跟手算不一致。原因商品价格用了 float 或 money 类型。float 是近似存储二进制浮点数在某些小数上无法精确表示money 类型虽然在显示上做了四位小数控制但做除法或累加时仍然会出现精度损失。解决所有金额和单价字段统一用 decimal。decimal(10,2) 表示总长度 10 位、小数 2 位能满足绝大多数零售场景。注意 decimal 的精度由第一个参数决定如果业务金额可能超过 8 位整数要用 decimal(14,2) 或更大否则数据库会报溢出或被截断。这也是第 3 章字段表里坚持用 decimal 的原因。5.5 SQL Server 2019 跑不了论文里的老脚本现象把 2005 的库脚本拿到 SQL Server 2019 上执行提示语法错误或兼容级别不支持或者附加 .mdf 文件失败。原因SQL Server 2005 的某些语法如 IMAGE、TEXT 类型或旧版备份格式在新版本里语法虽然保留但部分行为变了。另外备份文件 .bak 有版本限制2005 的备份不能直接还原到 2019。解决不用还原 .bak只拿文档中的 CREATE TABLE 脚本在新库上重建再手工导入数据。这是最干净的做法。如果脚本里有 IMAGE 或 NTEXT 这类老类型改成 VARBINARY(MAX) 或 NVARCHAR(MAX)。兼容级别如果遇到查询行为差异可以用 ALTER DATABASE SupermarketPurchase SET COMPATIBILITY_LEVEL 120 调整但核心思路还是脚本重建。6. 进阶把设计文档改造成可运行项目的三条捷径6.1 反向生成先建库再写 Bean比顺着论文写快一倍论文的目录顺序是「分析 → 设计 → 实现」但动手做项目时不要照着这个顺序走。正确的路径是先把第 3 章的建表脚本跑通然后把表结构反向生成 JavaBean。MyEclipse 和 IDEA 都有数据库逆向生成 POJO 的功能表结构和字段注释齐全时生成的实体类基本不用改。有了实体类再写 DAO 层的增删改查最后才写 JSP 页面。这样所有代码都建立在真实表结构之上不会出现在页面里写错字段名的低级错误。我一般会先用 SQL 脚本把数据库建出来再逐个模块实现先用户登录再供应商管理再商品管理然后才是库存和订单。前三个模块都是单表增删改查代码模式完全一致写熟之后库存和订单这种多表操作会顺手很多。6.2 三个值得做的小改造第一在 DAO 层外面套一个轻量级框架比如直接用 Spring JDBC 或 MyBatis把连接管理和 SQL 参数绑定从代码里抽出去。原来每个 Bean 里都有一段一模一样的 Class.forName 和 DriverManager.getConnection换成数据源之后这些重复代码可以全删。第二给采购订单表加一个状态机字段把「草稿 → 已审核 → 已入库 → 已结算」四个状态串起来。原文档里订单状态比较粗只有 0 和 2 两个值真实业务还缺待审核环节。加状态字段的同时在 JSP 页面上按状态控制按钮的显隐能减少不少误操作。第三用视图或查询语句做库存预警。商品表里已经有 stock_lower 字段可以把库存量低于预警值的商品做成一个视图在主页上直接展示列表库管员登录就能看到哪些商品需要补货。这个功能改动量小但演示效果很好答辩或交付时都拿得出手。6.3 验证系统是否完整的检查清单项目做完后建议按下面这个清单走一遍比对着论文目录逐章读要快登录失败和成功两个分支是否都能走到供应商添加后能否在商品页面下拉框里看到创建采购订单后库存是否增加了对应数量库存增加后订单状态是否变成已入库删除一个被订单引用的商品数据库是报错还是出现孤儿数据。前四条是业务流程第五条是数据完整性。当初我在给一家小超市做类似系统时跳过了第五条结果删了一个停售商品历史订单的明细跟着丢了。从那以后我每次做完任何带外键的页面都强制自己先试一遍删除被引用记录的场景再继续往下开发。希望这些踩过的坑能帮你省下几个晚上的调试时间也希望这份文档在你的项目里真正派上用场。本文还有配套的精品资源点击获取