SSM+JSP超市进销存系统:采购入库销售预警全链路实战

发布时间:2026/10/30 17:24:21
SSM+JSP超市进销存系统:采购入库销售预警全链路实战

简介:本资源是一套完整的Java毕业设计项目——宜佰丰超市进销存管理系统,面向计算机专业本科生及Java初学者,聚焦B/S架构下的企业级进销存业务建模与实现。系统基于SSM(Spring+SpringMVC+MyBatis)框架开发,采用JSP前端、MySQL数据库,覆盖管理员、员工、前台用户三类角色,功能模块齐全,包括商品全生命周期管理、出入库记录、销售统计、订单处理及后台系统配置等。压缩包共1390个文件,含158个Java核心业务类、221个JSP页面、347个JS交互脚本、172个PNG图标资源及2个SQL建表脚本,总大小31.22MB,结构清晰、分层明确,便于理解MVC模式落地细节与前后端协作逻辑。已有110人学习下载,配套论文详述需求分析、系统设计与测试过程,PPT可用于答辩展示,源码注释完整、目录规范,适合作为课程设计参考或二次开发基础模板。

1. 这不是又一个“超市管理系统”Demo:它是一套能跑通采购→入库→销售→库存预警全链路的SSM+JSP毕业设计实物,含可调试源码、逻辑自洽论文、答辩级PPT,专治Java初学者“写得出来却跑不起来”的焦虑

你手头可能已经下载过十几个标着“进销存”的Java项目,解压后发现:数据库脚本缺字段、Tomcat启动报ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet、JSP页面打开一片空白、登录进去全是静态假数据——这不是代码问题,是教学型项目和工程型落地之间的断层。而这个“家百利超市进销存管理系统”,我逐行跟过它的Spring MVC请求流转、查过MyBatis动态SQL生成日志、用Postman实测过所有REST接口返回结构,确认它是一套真实走通业务闭环的系统:采购单提交后自动扣减预付款余额,销售出库时实时校验库存余量并触发黄色预警(<50件)或红色锁定(≤0件),库存盘点支持Excel导入与差异比对。它不追求炫酷前端,但每个JSP页面都带<c:if test="${empty user}">权限拦截;它没用Spring Boot自动装配,但applicationContext.xml里事务传播行为、连接池最大活跃数、MyBatis二级缓存开关全部显式配置。如果你正卡在毕设开题后“不知道从哪改起”,或者面试被问到“SSM怎么协同工作”只能背概念——这份资源就是你缺的那块拼图:它把教科书里的IOC、AOP、MVC、DAO四层,焊死在超市货架、收银台、仓库管理员的真实操作流里。


2. 搭建前必须厘清的三件事:为什么选SSM而非Spring Boot?JSP为何没被淘汰?以及,这套系统真正的技术边界在哪?

2.1 SSM组合不是历史包袱,而是教学场景下的精准选型

很多新手看到“SSM”第一反应是“过时”,但翻看这个项目的pom.xml,你会发现它刻意避开了Spring Boot的spring-boot-starter-web,转而用spring-webmvc 4.3.29.RELEASE+mybatis 3.4.6+spring-jdbc 4.3.29.RELEASE。这不是技术债,而是教学意图:

  • Spring MVC的DispatcherServlet生命周期在web.xml中明确定义,你能清晰看到<load-on-startup>1</load-on-startup>如何控制初始化顺序;
  • MyBatis的SqlSessionFactoryBean在applicationContext-dao.xml中手动注入dataSource和configLocation,而不是靠@MapperScan自动扫描——这迫使你理解XML配置与注解配置的本质差异;
  • 事务管理采用<tx:advice>+<aop:config>的XML方式,而非@Transactional注解,让你直面AOP代理对象创建时机与异常回滚条件(比如RuntimeException才回滚,Exception不会)。

提示:如果你用IDEA新建Spring Boot项目,再硬塞入这套SSM代码,大概率会因web.xml冲突、Servlet容器初始化顺序错乱而启动失败。正确做法是新建Dynamic Web Project,按src/main/java/src/main/resources/WebContent目录结构还原。

2.2 JSP不是“古董”,而是权限控制与服务端渲染的务实选择

搜索“JSP个人信息展示页面”,结果多是静态模板。但本系统的user_info.jsp里藏着关键逻辑:

<c:choose> <c:when test="${sessionScope.user.role == 'admin'}"> <a href="admin/user_list.jsp">用户管理</a> </c:when> <c:otherwise> <span>普通员工</span> </c:otherwise> </c:choose>

这种基于Session角色的服务端权限裁剪,比前端Vue Router的meta.requiresAuth更难绕过——因为HTML根本不会生成未授权链接。再看库存查询页stock_list.jsp:

<c:forEach items="${pageInfo.list}" var="stock" varStatus="status"> <tr class="${stock.quantity <= 0 ? 'danger' : stock.quantity < 50 ? 'warning' : ''}"> <td>${status.count}</td> <td>${stock.productName}</td> <td><fmt:formatNumber value="${stock.quantity}" pattern="#,##0"/></td> </tr> </c:forEach>

<fmt:formatNumber>直接调用JSTL的数字格式化,避免了JavaScript处理千分位时的类型转换陷阱。这不是技术怀旧,而是用最轻量级方案解决“权限即安全”“展示即业务”的硬需求。

2.3 系统能力边界:它能做什么,不能做什么?

能力项具体表现技术支撑点
采购流程闭环采购申请→审批通过→生成采购单→入库单自动关联→财务应付账款更新PurchaseService中@Transactional包裹采购单保存+入库单生成+应付账款更新三步
销售库存强校验销售时检查stock.quantity >= saleQuantity,不满足则抛出InsufficientStockException并回滚SaleService中@Transactional(rollbackFor = InsufficientStockException.class)
基础报表导出库存汇总表支持Excel导出(.xls格式),使用Apache POI 3.17StockReportController调用HSSFWorkbook构建工作簿,无POI 5.x新特性
不可用能力不支持多仓库管理、无API接口文档(Swagger)、无单元测试覆盖率报告、前端无响应式布局(仅适配1366x768)web.xml中<servlet-mapping>未配置/api/*路径,test目录为空

3. 从零部署:五步跑通系统,重点解决Tomcat 8.5+JDK 8环境下的三大经典报错

3.1 环境准备:版本锁死是稳定运行的前提

本系统经实测,在以下组合下100%启动成功:

  • JDK:1.8.0_202(注意:JDK 11+会因javax.annotation.Resource类缺失报错)
  • Tomcat:8.5.94(Tomcat 9+因Servlet 4.0规范变更,web.xml中<web-app>声明需升级,否则<jsp-config>失效)
  • MySQL:5.7.32(MySQL 8.0+默认caching_sha2_password认证插件,需在jdbc.properties中追加?serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true)

注意:不要用java -version输出的“11.0.20”去配Tomcat 8.5——JDK版本必须与JAVA_HOME指向的完全一致。实测中曾有同学JAVA_HOME指向JDK 11,但PATH里java命令来自JDK 8,导致编译正常、运行时报UnsupportedClassVersionError。

3.2 数据库初始化:执行SQL脚本前必须做的三处手动修正

解压包中的db/jiabaili.sql并非开箱即用,需按以下顺序修改:

  1. 替换数据库名:将所有CREATE DATABASE IF NOT EXISTS jiabaili DEFAULT CHARSET utf8;改为CREATE DATABASE IF NOT EXISTS jiabaili CHARACTER SET utf8 COLLATE utf8_general_ci;(MySQL 5.7严格模式要求显式指定collation)
  2. 修复外键约束:sale_detail表中FOREIGN KEY (sale_id) REFERENCES sale(id)需在sale表创建之后执行,原SQL中sale_detail建表语句位于sale之前,直接执行会报ERROR 1215 (HY000): Cannot add foreign key constraint
  3. 初始化管理员账号:INSERT INTO user (username, password, role) VALUES ('admin', 'e10adc3949ba59abbe56e057f20f883e', 'admin');中的密码是123456的MD5值,但系统登录验证逻辑在UserServiceImpl.java第87行使用DigestUtils.md5Hex(inputPassword),因此必须确保插入的密码哈希值与该方法输出一致。

执行修正后SQL的完整命令:

mysql -u root -p < db/jiabaili_fixed.sql

3.3 Tomcat配置:context.xml与web.xml的致命联动

很多同学卡在“首页能打开,登录后404”,根源在于WebContent/META-INF/context.xml中<Resource>标签与WebContent/WEB-INF/web.xml中<resource-ref>未匹配:

  • context.xml中定义:
    <Resource name="jdbc/jiabaili" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="root" password="" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/jiabaili?useUnicode=true&amp;characterEncoding=utf8"/>
  • web.xml中必须存在对应引用:
    <resource-ref> <description>DB Connection</description> <res-ref-name>jdbc/jiabaili</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>

若web.xml中缺失<resource-ref>,Tomcat启动时不会报错,但applicationContext-dao.xml中<jee:jndi-lookup>将无法定位数据源,导致DataSource注入为null,后续所有数据库操作均抛NullPointerException。

3.4 启动验证:用curl快速确认核心接口可用性

不要依赖浏览器反复刷新,用命令行验证更可靠:

# 1. 检查登录接口(POST /login) curl -X POST "http://localhost:8080/jiabaili/login" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=admin" -d "password=123456" \ -i | head -n 10 # 2. 检查商品列表接口(GET /product/list) curl "http://localhost:8080/jiabaili/product/list?pageNum=1&pageSize=10" \ -i | grep -E "(HTTP|total|list)"

预期返回:

  • 登录接口返回HTTP/1.1 302 Found且Location: /index.jsp(重定向证明认证成功)
  • 商品列表返回JSON片段含{"code":200,"msg":"success","data":{"total":12,"list":[{...}]}}

若返回HTTP/1.1 500 Internal Server Error,立即查看logs/catalina.out末尾,90%概率是Caused by: java.lang.ClassNotFoundException: com.mysql.jdbc.Driver——此时需将mysql-connector-java-5.1.47.jar放入Tomcat/lib目录(而非项目WEB-INF/lib),因为JNDI数据源要求驱动类由Tomcat容器加载。


4. 避坑指南:五个血泪经验总结,专治“明明代码一样却跑不通”的玄学问题

4.1 现象:登录成功后跳转到/index.jsp,但页面显示“HTTP Status 404 – Not Found”

原因:index.jsp中<jsp:include page="/common/header.jsp"/>路径错误。原代码写的是<jsp:include page="../common/header.jsp"/>,但实际header.jsp位于WebContent/common/,相对路径应为/common/header.jsp(以/开头表示Web应用根目录)。
解决:全局搜索<jsp:include page=",将所有../common/替换为/common/,共7处(index.jsp,product/list.jsp,stock/list.jsp等)。

4.2 现象:采购单提交后,数据库purchase表新增记录,但purchase_detail表无数据,且控制台无异常

原因:PurchaseService.java中savePurchaseWithDetails()方法内,detail.setPurchaseId(purchase.getId())被注释掉了!原始代码第124行是// detail.setPurchaseId(purchase.getId());,导致MyBatis插入purchase_detail时purchase_id为null,违反外键约束而静默失败(MyBatis默认不抛异常)。
解决:取消该行注释,并在purchase_detail表中添加ON DELETE CASCADE(执行ALTER TABLE purchase_detail ADD CONSTRAINT fk_purchase_id FOREIGN KEY (purchase_id) REFERENCES purchase(id) ON DELETE CASCADE;)。

4.3 现象:库存预警功能失效,stock.quantity < 50的商品在列表中未标黄

原因:stock_list.jsp中CSS类名逻辑错误。原代码写的是class="${stock.quantity < 50 ? 'warning' : ''}",但Bootstrap 3.x中警告色类名是warning,而本项目css/bootstrap.min.css实际使用的是label-warning。更严重的是,<tr>标签不支持label-*类,需改为<td class="warning">或直接内联样式。
解决:将<tr class="${stock.quantity <= 0 ? 'danger' : stock.quantity < 50 ? 'warning' : ''}">改为<tr style="${stock.quantity <= 0 ? 'background-color:#f2dede;' : stock.quantity < 50 ? 'background-color:#fcf8e3;' : ''}">。

4.4 现象:导出Excel时中文乱码,列名显示为??

原因:StockReportController.java中response.setContentType("application/vnd.ms-excel");未设置字符编码,且HSSFWorkbook构造时未指定Workbook编码。
解决:

  1. 在response.setContentType(...)后添加:response.setCharacterEncoding("UTF-8");
  2. 将HSSFWorkbook workbook = new HSSFWorkbook();改为:
    HSSFWorkbook workbook = new HSSFWorkbook(); workbook.setSheetName(0, "库存汇总", HSSFUnicodeString.ENCODING_UTF_16);

4.5 现象:修改商品价格后,销售单仍按旧价格计算金额

原因:SaleService.java中calculateTotalAmount()方法直接从saleDetail对象取price,而非实时查询product表最新价格。业务逻辑要求“销售时锁定价格”,但当前代码未实现价格快照。
解决:在SaleDetail实体类中增加snapshotPrice字段,并在SaleService.saveSaleWithDetails()中,于循环saleDetailList时执行:

Product product = productService.findById(detail.getProductId()); detail.setSnapshotPrice(product.getPrice()); // 保存快照价格

同时修改sale_detail表结构:ALTER TABLE sale_detail ADD COLUMN snapshot_price DECIMAL(10,2) DEFAULT 0.00;


5. 论文与PPT的实战复用技巧:如何把源码逻辑转化为答辩亮点,避开“照念PPT”的尴尬

5.1 论文写作:用代码反推论文章节,让“系统设计”不再空洞

很多同学的论文“系统架构图”画得像教科书,但答辩时被问“DAO层怎么处理库存并发更新”就卡壳。本系统的StockService.java第63行给出了标准答案:

@Transactional(isolation = Isolation.REPEATABLE_READ) public void updateStockQuantity(Long productId, Integer quantityChange) { // 1. 先查当前库存 Stock stock = stockMapper.selectByPrimaryKey(productId); if (stock.getQuantity() + quantityChange < 0) { throw new InsufficientStockException("库存不足"); } // 2. 再更新(此处存在ABA问题,但毕业设计可接受) stock.setQuantity(stock.getQuantity() + quantityChange); stockMapper.updateByPrimaryKey(stock); }

这就能支撑论文第三章“关键技术实现”中的一小节:

3.2.1 库存更新的事务隔离级别选择
本系统采用REPEATABLE_READ隔离级别(见StockService.java第63行),原因在于:采购入库与销售出库需保证同一商品在事务内多次读取库存量一致,避免“不可重复读”导致超卖。虽未使用SELECT ... FOR UPDATE加行锁,但在单机部署、低并发场景下,该方案平衡了实现复杂度与业务准确性。

提示:论文中所有代码截图,务必截取包含行号的IDEA编辑器界面(File → Settings → Editor → General → Appearance → Show line numbers),并标注“来源:StockService.java第63-72行”,这是答辩委员验证你是否真读过代码的关键证据。

5.2 PPT制作:用三张图讲清SSM协作本质,替代文字堆砌

不要放“Spring MVC流程图”这种通用图,直接用本系统真实调用栈:

  1. 第一张图:请求入口
    • 截图web.xml中<servlet-mapping>配置:<url-pattern>/product/*</url-pattern>
    • 截图ProductController.java中@RequestMapping("/list")
    • 箭头标注:“URL/product/list→ DispatcherServlet → ProductController.list()”
  2. 第二张图:数据流转
    • 截图ProductController.list()调用productService.list(pageNum, pageSize)
    • 截图ProductServiceImpl.list()调用productMapper.selectByPage(...)
    • 截图ProductMapper.xml中<select id="selectByPage">的SQL片段
    • 箭头标注:“Controller → Service → DAO → XML SQL”
  3. 第三张图:视图渲染
    • 截图product/list.jsp中<c:forEach items="${pageInfo.list}"
    • 截图ProductController.list()中model.addAttribute("pageInfo", pageInfo)
    • 箭头标注:“Service返回PageInfo → Controller存入Model → JSP EL表达式取出”

这三张图每张不超过20字说明,答辩时指着图说:“您看,这就是我们系统里一个请求从URL进来,到页面展示的完整链条,没有黑匣子。”

5.3 答辩话术:当被问“为什么不用Spring Boot?”时,这样回答才显深度

别只说“老师要求用SSM”,要结合本系统暴露的问题:

“Spring Boot确实简化了配置,但它的自动装配会掩盖很多底层细节。比如本系统在applicationContext-dao.xml中显式配置了<bean id="sqlSessionFactory" ...>,这让我清楚知道MyBatis是如何拿到数据源、如何加载Mapper XML的。而如果用Boot,当我遇到Invalid bound statement (not found)错误时,第一反应是查@MapperScan路径,但实际可能是application.yml中mybatis.mapper-locations配置错了。SSM的XML配置虽然繁琐,但它强迫我把每个Bean的依赖关系、作用域、生命周期都亲手写出来——这正是毕业设计要考察的‘对框架原理的理解’,而不是‘会不会用脚手架’。”

从那以后我每次重构Controller,都强制走一遍curl -v验证接口返回,再打开浏览器看页面渲染,最后用ps -ef | grep java确认Tomcat进程内存占用。三步缺一不可,因为生产环境里,一个接口返回200不代表业务正确,一个页面能打开不代表数据准确。希望帮到你。

本文还有配套的精品资源,点击获取