
简介一套面向毕业设计场景的JSPSSM生鲜超市进销存管理系统完整源码包适合计算机相关专业学生用于课程设计、毕业答辩或项目二次开发。系统覆盖商品信息管理、基本档案管理、进货采购管理、销售出库管理四大模块前端采用JSP后端为SSM框架数据库使用MySQL并支持Eclipse、MyEclipse、STS、IDEA等常见开发工具。资源包共428个文件约35.79MB包括82个Java源文件与对应class文件、60个JSP页面、77个依赖JAR包、38个XML配置、SQL数据库脚本、论文文档、环境工具包及同框架项目的安装教程视频结构完整、便于直接导入运行。目前已有89人学习下载。内含源码、数据库脚本、论文、工具包和教程尤其针对商品监管不合格提示、销售出库记录、供应商管理等业务细节有完整实现可帮助理解SSM整合流程与进销存业务逻辑。1. 生鲜超市进销存为什么值得做这个标题到底交付了什么如果你正在找毕业设计题目看到「JSP SSM 生鲜超市进销存管理系统」这个组合第一反应可能是又是进销存做了八百遍的题目。但生鲜超市和普通进销存有个本质差别——商品会坏。蔬菜放三天就黄叶牛奶临期要打折冻品化冻就得报损。这套系统把 SSMSpring Spring MVC MyBatis这套经典 JavaWeb 技术栈架在生鲜场景上核心不是管库存数量而是管批次、管保质期、管损耗。标题里「B源码含文档含教程」的意思是交付物齐全源码能跑、有设计文档可交、有教程能照着复现。适合三类人正在选题的本科生、想快速搭一套完整 SSM 项目练手的开发者、以及需要一套带生鲜业务亮点的毕设去答辩的人。2. SSM 在进销存里的分工从三层架构到业务闭环2.1 为什么是 SSM 而不是 Spring Boot先回答一个大概率会纠结的问题现在企业里新项目基本都上 Spring Boot 了毕设还写 JSP SSM 是不是过时我的看法是选题不是选最新是选最能讲清楚的。SSM 的好处在于三层架构界限分明——Controller 管请求分发Service 管业务逻辑Mapper 管数据库操作每一层都是显式写出来的。这在毕业设计答辩里特别占优势评委问「你的事务放在哪一层」你直接指到 Service 类上的Transactional就行不用像 Spring Boot 那样在一堆自动配置里找。另一个现实原因是从零搭建一个 SSM 项目能完整覆盖框架整合过程Spring 容器怎么加载、Spring MVC 怎么拦截请求、MyBatis 怎么和 Spring 整合每一步都是手动配置出来的。这个过程本身就是毕设的工作量和技术含量。如果直接用 Spring Boot框架帮你干了大部分事反而没东西可写。所以选 SSM 不算退而求其次是把它当成学习产出的一部分。至于 JSP 做页面放在当下看确实复古但毕设场景里它有个不可替代的优势后端传数据到页面直接用 EL 表达式和 JSTL 标签就能渲染不需要单独写 Vue 和接口。更关键的是——答辩现场如果你用前后端分离评委要看你启动前端工程、看接口文档、看跨域配置而 JSP 项目启动 Tomcat 就能点着页面讲功能演示成本低很多翻车概率也低。常见做法是 JSP 只在表现层输出数据业务逻辑全部收敛在 Service 层这样就算页面丑一点代码结构是经得起问的。2.2 生鲜进销存和普通进销存的三点差异做这类型项目之前建议先把业务差异想清楚否则写出来就是一个换皮的标准进销存答辩时不好讲。生鲜场景下至少有三个地方不一样。一是库存要按批次管理。同一种牛奶上午进的批次保质期到月底下午进的批次保质期到下月中旬不能混在一起算总库存否则临期商品无法识别。这种需求自然要求在库存表上带出生产日期、到期日这些字段而不只是「数量」。二是价格会动态调整。普通超市标价基本固定生鲜商品经常有早市价、晚市折扣、临期特价销售时不能直接按商品表里的基准价去算销售额。这就需要销售明细里冗余一份成交单价把当时实际卖多少钱记下来后续对账才不会扯皮。三是要有损耗处理路径。蔬菜水果在运输和存储过程中会有正常损耗、报损、退货这些不能直接扣库存了事要有独立的损耗单或报损记录保证账面库存和实际盘点能对上。很多初版系统把损耗逻辑塞进销售模块里结果月底统计毛利时怎么都对不上就是因为损耗混在了销售数据里。2.3 功能模块拆解与页面流转按照常见实现方案这套系统的功能可以拆成七个模块登录与用户管理、供应商管理、商品管理含分类、采购入库、销售出库、库存查询与盘点、报表统计。核心业务闭环是先维护供应商和商品档案再走采购单入库库存增加顾客下单走销售单出库库存减少库存低于预警线时提示补货生鲜批次临近保质期时进入预警列表。页面流转一般是这样的登录页验证通过后进入主框架页左侧是菜单树右侧是功能页面。采购业务流程是「采购单列表 → 新建采购单 → 选择供应商和商品 → 入库确认」销售流程是「销售单列表 → 新建销售单 → 加商品 → 结算出库」。这套流转逻辑和实际超市操作能对应上演示时也容易讲。整个代码包的基础包名通常按 com.项目名.controller、service、mapper、entity 这类结构切分Controller 只做参数接收和页面跳转Service 里写业务判断Mapper 里是 MyBatis 的接口和 XML 映射。理解了这条链路后面改代码和定位问题都会顺手很多。3. 数据库设计先解决生鲜的保质期与批次核心表结构与字段说明3.1 整体表结构一览进销存系统的数据库表数量一般在十张左右核心不是表多而是表之间的关系要符合业务流转。下面是一个在毕设项目中最常见的表清单字段做了精简落地时会根据教程里的 SQL 脚本微调表名作用关键字段admin_user系统用户id、username、password、real_namesupplier供应商id、supplier_name、contact、phonecategory商品分类id、category_namegoods商品档案id、goods_name、category_id、unit、purchase_price、sale_price、stock_warninggoods_batch商品批次库存id、goods_id、batch_no、quantity、purchase_price、produce_date、expire_date、supplier_idpurchase_order采购单id、order_no、supplier_id、total_amount、create_time、statuspurchase_item采购明细id、order_id、goods_id、quantity、price、amountsale_order销售单id、order_no、total_amount、create_time、statussale_item销售明细id、order_id、goods_id、quantity、price、amountloss_order损耗报损单id、goods_id、quantity、reason、create_time这个结构里最值得关注的是goods_batch表。很多新手做进销存都是 goods 表里直接挂一个 stock 字段库存不够就减进货就加。这个设计在生鲜场景一定翻车——因为同一种商品不同批次的进货价可能不同保质期也不一样如果都混在一个数字里后面既算不出临期商品也算不准毛利。引入批次表之后总库存变成了「所有批次 quantity 之和」卖出时按先进先出或指定批次扣减语义清楚代码也好写。3.2 商品表与批次表的核心建表语句以下是常见的建表语句形态可以直接对照教程里的 SQL 脚本理解CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, goods_name VARCHAR(100) NOT NULL, category_id INT, unit VARCHAR(20) DEFAULT 斤, purchase_price DECIMAL(10,2), sale_price DECIMAL(10,2), stock_warning INT DEFAULT 10, status TINYINT DEFAULT 1 ); CREATE TABLE goods_batch ( id INT PRIMARY KEY AUTO_INCREMENT, goods_id INT NOT NULL, batch_no VARCHAR(50), quantity INT DEFAULT 0, purchase_price DECIMAL(10,2), produce_date DATE, expire_date DATE, supplier_id INT );这里有几个设计细节值得多说一句。expire_date是冗余字段——理论上可以由produce_date 保质期天数推算出来但专门存一列会让后续写「查三天内过期商品」的 SQL 简单非常多直接WHERE expire_date BETWEEN ...就行不用每行都算日期加减。purchase_price在批次表里也存了一份卖货算毛利时用批次进价来匹配销售明细才是最接近真实情况的算法这也是为什么要给批次表保留供应商字段的原因——方便追溯这批货是谁供的。3.3 为什么销售明细必须存一份成交单价生鲜商品的价格是动态的上午十块一斤晚上八块一斤临期可能五块处理。如果销售出库时不做价格快照只记录商品 ID那么一个月后回看这张销售单你只能看到商品当前价格根本不知道当时卖了多少钱。常见的正确做法是 sale_item 表里显式存price和amount两个字段下单那一刻从页面传入的成交价落库。采购明细也是一样进货价可能随供应商供货批次波动所以 purchase_item 里也要存price。这样做的好处是对账和毛利统计都是基于事实数据而不是推断数据。很多翻车的报表问题就是出在这里——报表里的金额是「现算」的而不是「存档」的。3.4 预置数据与初始化账号毕设项目一般会在 SQL 脚本里预置一个管理员账号常见做法是admin / admin123密码不会用明文存储通常是 MD5 处理后入库。导入数据库后第一件事就是用这个账号登录系统然后去「用户管理」里改密码。源码包里自带的初始化脚本还会插入少量商品分类和供应商数据目的是让页面不至于空荡荡演示时也能直接走采购到销售的完整流程。4. 把源码跑起来环境版本搭配与三步启动配置4.1 JDK、Tomcat、MySQL 的版本怎么搭不容易翻车这个项目用的是老牌技术栈版本选得对不对直接决定你能不能跑起来。常见的稳定搭配是JDK 1.8、Tomcat 8.5、MySQL 5.7、Maven 3.6集成开发环境用 IDEA。组件推荐版本说明JDK1.8SSM 项目普遍基于 JDK8 编译不要用 17 及以上Tomcat8.5与 JDK8 配合最稳9.0 也能用但没必要冒险MySQL5.7如果本地只有 8.0 也可以但要换驱动和连接参数Maven3.6只要能正常下载依赖就行3.8 以上也兼容IDEA2020 之后任意版本社区版也能开 Maven 项目专业版只有 Tomcat 集成更顺这里最容易踩的位置是 MySQL 8.0。很多新电脑直接装了 8.0项目里的 mysql-connector-java 还是 5.x 的旧驱动连数据库时直接报ClassNotFoundException或者Public Key Retrieval is not allowed。如果你是 8.0 数据库按后面的避坑章节处理即可。4.2 导入工程与初始化数据库拿到源码压缩包后第一件事不是急着打开 IDEA而是先把数据库准备好。先在 MySQL 里建一个库然后导入 SQL 脚本mysql -u root -p CREATE DATABASE fresh_manager DEFAULT CHARACTER SET utf8mb4; exit; mysql -u root -p fresh_manager /your/path/fresh_manager.sql这段命令的逻辑是先登录 MySQL 创建名为 fresh_manager 的数据库指定 utf8mb4 字符集是为了让中文存储不出现乱码然后退出交互式登录用重定向把 SQL 脚本灌进这个新建的库。如果 SQL 脚本文件里有CREATE DATABASE和USE语句那第一步手动建库可以省略直接执行第二条命令导入即可。导入完成之后验证一下表结构是否齐全mysql -u root -p USE fresh_manager; SHOW TABLES;正常会看到前面表格里列出的那几张表。如果SHOW TABLES结果为空或者少表大概率是 SQL 脚本中途报错中断了检查脚本里有没有DROP DATABASE之类的破坏性语句或者本地 MySQL 的 sql_mode 是否限制了某条语句执行。4.3 改数据库连接配置SSM 项目的数据源配置一般在src/main/resources下的 properties 文件里名字可能是db.properties或jdbc.properties。找到它并改成你本地数据库的账号密码jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/fresh_manager?useUnicodetruecharacterEncodingutf8useSSLfalse jdbc.usernameroot jdbc.password你的数据库密码这四行分别定义了 JDBC 驱动类名、数据库连接地址、用户名和密码。characterEncodingutf8是保证写入数据库的中文不乱码useSSLfalse是为了避免本地连接时出现的 SSL 警告。如果用的是 MySQL 8.0需要把驱动类名改成com.mysql.cj.jdbc.Driver同时在连接地址末尾加上serverTimezoneAsia/Shanghai否则会报时区错误。这个坑几乎每个人都会踩一次后面避坑章节专门展开。4.4 启动前检查依赖是否完整SSM 项目导入 IDEA 后Maven 会自动下载依赖。如果网络不好或本地仓库缺包经常出现依赖下载一半的情况。启动前可以先在 IDEA 终端跑一次编译mvn clean package -DskipTests这条命令会清理旧的编译产物、重新编译源码并打包成 war 文件。-DskipTests是跳过测试代码毕设项目一般没有写单元测试加上它省时间。如果这一步报错先去pom.xml里检查依赖坐标是否正确——最常见的报错是仓库里找不到对应版本的依赖把版本号改成本地 Maven 仓库里已有的版本即可。打包成功后就可以配 Tomcat 启动了。常见操作是把 war 包拷贝到 Tomcat 的 webapps 目录后启动 Tomcat或者直接在 IDEA 里配置本地 Tomcat 运行。推荐后者因为 Debug 模式看链路更方便。启动成功后访问http://localhost:8080/fresh_manager/能跳转到登录页说明项目跑通了。如果端口不是 8080看一下 Tomcat 配置里改成了多少。默认管理员账号在 SQL 脚本里预置通常是 admin 加一串初始密码具体以文档里的说明为准。5. 避坑排查跑通后最常翻车的五个点5.1 页面 404 或者 ClassNotFound依赖没进 lib现象Tomcat 启动没报错但一访问项目地址就 404或者控制台报ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet。原因IDEA 在部署 Web 项目时Artifacts 里的 WEB-INF/lib 没有打包 Maven 依赖。这个问题在导入别人的项目时特别常见——IDEA 识别了 Maven 结构但部署配置没有把依赖带上。解决打开 File → Project Structure → Artifacts选中当前项目的部署描述在右侧 Available Elements 里找到项目依赖的库右键选 Put into WEB-INF/lib。然后重新构建部署。这个操作做完刷新页面就正常了。顺手看一眼 WEB-INF 目录下有没有 class 文件如果连 class 都没有那是编译输出目录没配对把 Module 的编译输出路径改到 WEB-INF/classes 下。5.2 MySQL 8 连不上数据库驱动和时区双重问题现象启动日志里报No suitable driver found或者The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。原因本地装的是 MySQL 8.0但项目里用的还是 5.x 的驱动包或者连接参数里没带时区信息。MySQL 8.0 对时区要求变严格了不显式指定就会报错。解决pom.xml 里确认 mysql-connector-java 的版本改成 8.0.26 或相近版本然后在 jdbc.properties 里把驱动类名改为com.mysql.cj.jdbc.Driver连接地址末尾加serverTimezoneAsia/Shanghai。改完记得重新构建项目让新 jar 包进入 WEB-INF/lib。这里有个容易忽略的小细节如果改完 pom 但没重新构建 Artifacts旧 jar 仍然在部署目录里照样连不上。5.3 页面中文乱码三层编码要统一现象页面显示中文全是问号或者存入数据库后变成乱码但控制台输出正常。原因JSP 页面编码、Tomcat 请求编码、数据库表字符集三者没统一。最常见的组合是一处用了 UTF-8另一处用了 GBK或者数据库表是 latin1 字符集。解决按三层依次排查。JSP 页面头部要写pageEncodingUTF-8Tomcat 的 server.xml 里给 Connector 加URIEncodingUTF-8数据库表和库本身要确认是 utf8mb4 字符集。如果表建出来已经是 latin1不要只改连接参数直接对表执行ALTER TABLE goods CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;执行完再把数据重新导入一次乱码问题基本能根治。平时写代码时也要注意前端提交过来的数据经过过滤器没有项目里一般会有一个字符编码过滤器配置在 web.xml确保它拦截了所有请求路径。5.4 库存数量对不上事务和控制逻辑的锅现象销售单创建成功后库存总数没扣减或者扣了两次采购入库同样操作两次数量翻倍。原因两个层面导致。一是 Service 方法里没有加事务Mapper 的更新操作各自独立提交中间任何一步报错库存就会停留在不一致的状态。二是控制逻辑写错了位置比如把库存扣减写在了循环里又套了判断或者页面重复提交了订单。解决事务层面在 Service 实现类的方法上显式加Transactional确保扣库存、生成销售明细、更新商品销量这几步在同一个数据库事务里。控制层面点击提交按钮后要立刻禁用按钮同时后端在创建订单前先查一下同号订单是否存在用订单号做幂等控制。这就避免了手抖双击造成两条销售单各扣一次库存的尴尬局面。5.5 启动端口被占用或者热部署没生效现象Tomcat 启动直接报Port 8080 was already in use或者改完代码刷新页面还是旧页面。原因上一轮 Tomcat 没关闭干净或者 IDEA 的热部署配置没开。Windows 下经常出现关掉了 IDEA 但 Java 进程还占着端口的情况。解决端口被占用的处理是开命令行执行netstat -ano | findstr 8080 taskkill /PID 对应PID /F先通过 netstat 找到占用 8080 端口的进程 PID再用 taskkill 强制结束。如果是热部署不生效检查 IDEA 里 Tomcat 配置的 On frame deactivation 选项是否选了 Update resources这是 JSP 页面即时刷新的关键Java 代码改动则需要 DevTools 或手动重启 TomcatSSM 项目没有自动热部署的底子别指望改个 Controller 就能自动生效。6. 给系统加一个保质期预警在毕设里做出业务亮点前面说过生鲜进销存和普通进销存的本质差异在保质期和损耗。如果只是把 CRUD 写完系统能跑但缺乏记忆点。这里有一个很容易落地、又能在答辩时讲出亮点的进阶功能保质期预警。思路不复杂Spring 的Scheduled注解就能实现定时任务。在 Service 层写一个方法每天凌晨扫描一次 goods_batch 表把到期日距今小于等于三天的批次找出来写入一张预警通知表或者直接更新商品表的预警状态字段。页面端在登录后的主框架页加一个提醒区域显示「有 N 个批次临近保质期」。核心代码结构如下Component public class ExpireCheckTask { Autowired private BatchMapper batchMapper; Scheduled(cron 0 0 2 * * ?) public void checkExpiringBatches() { ListGoodsBatch list batchMapper.selectExpiringBatches(3); for (GoodsBatch batch : list) { batchMapper.updateExpireWarning(batch.getId(), 1); } } }这段代码逻辑是Scheduled里的 cron 表达式表示每天早上两点执行一次selectExpiringBatches(3)查询出所有剩余保质期小于等于三天的批次对查出来的批次把预警标记置为 1。参数 3 就是预警阈值你可以根据业务场景调成 1 或者 7。Mapper 里对应的查询 SQL 是select idselectExpiringBatches resultTypeGoodsBatch SELECT * FROM goods_batch WHERE expire_date BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL #{days} DAY) AND quantity 0 /select这段 SQL 的条件逻辑是过期时间在现在到 N 天之后之间并且还有剩余库存。之所以排除quantity 0的批次是为了避免已经卖完的批次还打扰预警列表。验证这个方法很简单造一条测试数据把 expire_date 改成明天手工调用一次任务再去页面看预警区域有没有出现这条记录。这个动作本身就是答辩演示时的绝佳素材——你不仅做了预警功能还有验证过程和数据支撑。我在自己做类似项目时有个血泪经验最早把保质期逻辑只做在商品表上以为存一个总保质期天数就行后来发现同一商品不同批次根本没法区分最后全部重构才理顺。所以如果你按这个方向做一定把批次表作为核心商品表只放档案信息所有库存动作落到批次维度上。这个设计决策在答辩时讲出来比堆功能更有说服力。除此之外还可以把预警做成可配置的在系统参数表里存一个 warn_days 字段让管理员自己设置提前几天提醒。改动量不大但功能完整度会明显上台阶。希望这些内容帮到你把毕设做成一个敢在演示时点开每个页面的作品。本文还有配套的精品资源点击获取