SSM+JSP超市进销存毕设项目源码复现与环境配置避坑指南

发布时间:2026/10/27 7:13:05
SSM+JSP超市进销存毕设项目源码复现与环境配置避坑指南 简介一套基于SSM和JSP的超市进销存管理系统完整毕业设计资源面向Java方向学生和开发者适合毕业设计、课程设计及实际项目参考。系统采用Java、MySQL和B/S结构覆盖管理员、员工、前台三类角色包含商品信息管理、入库出库记录、销售统计、订单管理等核心业务模块。压缩包共包含1390个文件以Java源码、JSP页面、JS脚本、CSS样式为主同时提供数据库SQL脚本和论文、PPT文档资源包大小31.22MB目录结构完整清晰。目前已有一百一十人学习可借助配套论文了解需求分析与设计思路结合源码快速搭建并二次开发对掌握SSM框架整合和进销存业务流程有较强参考价值。1. 拿 SSM JSP 做超市进销存这份毕设源码到底值不值得复现家百利超市进销存管理系统是典型的 java 毕业设计选题技术栈锁死在 SSMSpring SpringMVC MyBatis加 JSP 这套经典组合上。比起网上那些只有几个首页的空壳项目这份资源带源码、论文和答辩 PPT业务覆盖进货、销售、库存、供应商、报表统计几乎是照着毕业设计的评分点设计的。适合两类人一类是 java 基础一般、需要一个完整项目撑起毕设的应届生另一类是工作后想补 SSM 全流程、拿现成代码做二次开发的初级工程师。值不值得复现关键看它能不能在你的电脑上跑起来、论文能不能改得不像抄的这篇就把这两件事讲透。2. 项目骨架与核心业务从表结构到进销存主链路2.1 数据库设计六张核心表撑起整个进销存进销存系统的数据模型没有太高深的技巧核心就一句话商品、单据、库存三者对账不能乱。这份项目里的表结构我拆过一遍典型设计是六张主表外加几张辅助表如退货表、操作日志表。先看主表分工表名职责关键字段product_info商品主档product_code唯一编码、product_name、purchase_price、sale_price、warn_limitsupplier_info供应商档案supplier_code、supplier_name、contact、phonepurchase_order进货单主表order_no、supplier_id、total_amount、create_time、user_idpurchase_order_detail进货单明细order_id、product_id、quantity、price、subtotalsale_order销售单主表order_no、member_id可空、total_amount、create_timesale_order_detail销售单明细order_id、product_id、quantity、price、subtotalstock_info库存表product_id、stock_count、last_update_time两张主表和两张明细表是典型的父子表结构进货单、销售单都走一主多明细。库存表单独拆出来而不是在商品表里直接存库存量这是这类项目最常见的做法理由很直接进货和销售都要更新库存单独一张表加行锁比频繁更新商品主表更安全也方便以后做库存流水审计。设计上有一个容易忽略的点商品表里存着 purchase_price 和 sale_price 两个价格字段但明细表里也有 price。为什么不做联表查询因为进货价可能随批次变化销售价也可能临场折扣明细表里的 price 是“成交价”主表里的 price 是“默认价”这两者一旦混用月底对账必翻车。你要是改这个项目千万别为省字段把明细表的 price 删掉。2.2 一条进货单怎么走通 Controller → Service → Mapper我拿到这种 SSM 项目的第一步是找一条完整业务链进货入库是最典型的一条从浏览器请求到数据库更新一共经过四层。先看进货单的 Service 层代码逻辑这类项目里写法基本一致Service public class PurchaseOrderServiceImpl implements PurchaseOrderService { Autowired private PurchaseOrderMapper purchaseOrderMapper; Autowired private PurchaseOrderDetailMapper detailMapper; Autowired private StockMapper stockMapper; Transactional Override public void createOrder(PurchaseOrder order, ListPurchaseOrderDetail details) { // 1. 生成单号日期 流水号例如 PO20240512001 order.setOrderNo(generateOrderNo()); order.setTotalAmount(calculateTotal(details)); purchaseOrderMapper.insert(order); // 2. 批量插入明细 for (PurchaseOrderDetail detail : details) { detail.setOrderId(order.getId()); detailMapper.insert(detail); } // 3. 更新库存存在则累加不存在则初始化 for (PurchaseOrderDetail detail : details) { stockMapper.addStock(detail.getProductId(), detail.getQuantity()); } } }这段代码有三个设计要点。第一Transactional挂在 createOrder 上保证主表、明细、库存三步操作要么全部成功要么全部回滚没有事务的话明细插入失败但库存已经加了对账时会多出一批“幽灵库存”。第二单号生成放在业务层而不是数据库自增是为了保证单号可读且不依赖数据库特性MySQL 自增主键只做内部 ID 用。第三stockMapper.addStock是原子操作SQL 类似UPDATE stock_info SET stock_count stock_count #{quantity} WHERE product_id #{productId}而不是先查出来再 set 新值避免并发下重复覆盖。Controller 层做的事情很薄接收前端表单参数、手动组装 order 和 details 两个对象然后调 service。注意它没有在 Controller 里写任何业务判断这是 SSM 项目的标准分层约定职责边界乱了的代码一眼就能看出来。2.3 销售出库与库存扣减先判断再扣还是扣完再查进货是加库存销售是减库存减库存比加库存多一道关卡库存够不够。不少新手在 Service 里写“先查库存if 够用再扣”在高并发的玩具项目里没问题但进销存系统的扣库存逻辑通常更严谨UPDATE stock_info SET stock_count stock_count - #{quantity} WHERE product_id #{productId} AND stock_count #{quantity}这行 SQL 把“判断”和“扣减”合并成一个原子操作影响行数为 0 说明库存不足或商品不存在Service 拿到结果后抛出业务异常回滚事务。为什么不先SELECT stock_count再判断因为两条语句之间有间隙两个请求同时读到同一行余额时都会通过判断最后库存变成负数。虽然超市进销存的并发量不大但作为毕设答辩老师看的是你有没有这个意识。销售出库流程中还有一个前置动作锁定库存还是在销售单生成时就扣减。这份项目走的是后者即提交销售单的同时扣减库存不设独立的“预留库存”状态——那种设计常见于电商系统超市收银场景用不到。你再顺带看一眼销售退货退货单要做的是反向操作给库存加回去同时记一笔负数金额的销售明细方便统计毛损。对账这么顺下来你就发现了整条主链路的难点不在 CRUD而在事务边界和库存更新方式。把这章的代码看明白后面改动和答辩说理都有底气。3. 把项目跑起来JDK、Maven、Tomcat 与数据库连接的完整步骤3.1 环境版本怎么配才不打架SSM JSP 是上了年纪的组合版本选不对最容易出幺蛾子。我建议直接按下面这套来都是经过大量项目验证的搭配组件推荐版本说明JDK1.8高版本 JDK 对 JSP 和 Tomcat 的兼容性反而容易出问题Maven3.6.x3.8 以上对中央仓库访问策略有调整没必要冒险Tomcat8.5.x内嵌 JSP 编译器和 JDK 8 配合最稳MySQL5.7 或 8.05.7 驱动用 com.mysql.jdbc.Driver8.0 要用新驱动IDEA2020 以上均可Ultimate 版自带 Tomcat 集成这里强调两点。第一JDK 不要上 17Tomcat 8.5 在 JDK 17 下能跑但 JSP 编译偶尔报模块访问错误折腾半天没有收益。第二MySQL 用 8.0 的话驱动类和连接串参数必须跟着改这个坑后面单独说你现在只要知道 5.7 和 8.0 的配置不通用。3.2 导入项目后要改的五个配置点项目导入 IDEA 后先别急着点启动按照下面顺序改配置能省掉后面大把排查时间。第一个必改的是数据库连接配置在 resources 目录下的 jdbc.properties 或 db.properties 里jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingutf8useSSLfalse jdbc.usernameroot jdbc.password123456MySQL 8.0 用户把驱动和连接串改成这样不然启动直接报连接失败jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password123456serverTimezone不指定的话MySQL 8.0 默认时区配置会导致日期字段插入报错allowPublicKeyRetrievaltrue是解决 8.0 使用 caching_sha2_password 认证时的公钥获取问题。这两个参数缺一个都会让你在启动阶段白费半小时。第二个是 MyBatis 的 mapper 扫描路径。检查 applicationContext.xml 里 MapperScannerConfigurer 或者 Mybatis-Spring 的配置bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.supermarket.dao / /beanbasePackage必须和项目里的 DAO 接口包路径完全一致少一层或多一层启动时 Spring 容器不会报错但一调接口就抛 Invalid bound statement (not found)。第三个是 SpringMVC 的注解扫描和视图解析器在 spring-mvc.xml 里context:component-scan base-packagecom.supermarket.controller / bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp/ / property namesuffix value.jsp / /bean这里要注意Controller 扫描包不能和 Service 扫描包重叠出歧义而且 JSP 文件必须放在 prefix 指定的目录下你看到页面 404 时优先检查这里。第四个是 Tomcat 的部署配置。IDEA 的 Run/Debug Configurations 里Deployment 选项卡的 Application context 建议设为/supermarket和数据库名区分开。这个值决定浏览器访问路径设为/则直接 ip:8080 访问设成/supermarket则访问路径为http://localhost:8080/supermarket/login.jsp。第五个是项目自身编码。检查 IDEA 右下角编码项目文件和 properties 文件统一 UTF-8否则中文乱码问题会在页面和数据库里同时出现到时候你分不清是 JSP 的问题还是数据库连接串的问题。3.3 从启动日志判断项目是否正常配置全部改完点启动按钮后不要干等页面看控制台日志按顺序确认三件事。第一Spring 容器有没有完整初始化看到ContextLoaderListener - Root WebApplicationContext: initialization completed说明 service 和 mapper 都装配成功了。第二MyBatis 有没有把 mapper 映射文件加载进去日志里能看到类似Mapped Statements collection already contains value的提示如果你看到的是Invalid bound statement异常回到上一节检查 namespace 和 DAO 包路径。第三Tomcat 有没有正常部署项目看到Deployment of web application directory ... has finished后访问登录页。一个常见翻车点Tomcat 已经启动了但页面 404。先确认访问地址的 context path 对不对再看 controller 的 RequestMapping 值最后看 JSP 是不是放在 WEB-INF 下。按这个顺序排查比到处改代码强得多。3.4 数据库初始化表结构和测试数据怎么导入启动项目前要把数据库建好。项目里一般带 supermarket.sql用 Navicat 或 MySQL 命令行导入mysql -u root -p -h localhost supermarket.sql我一般习惯用命令行而不是图形工具导入因为项目自带的 sql 有时带了建库语句图形工具执行容易报错。导入成功后确认一下所有表是否建出来、sys_user 表里是否有一条 admin 账号、product_info 是否又几条示例商品。没有测试数据的话登录后是空界面你也不知道系统到底好没好。sql 文件里如果没有示例商品自己手动插两条别跳过这步——后面调试业务链全靠这几条数据。4. 避坑这五个问题占了调试时间的 80%4.1 一启动就报 ClassNotFoundException: javax.servlet.jsp.jstl.core.Config现象Tomcat 启动过程中抛出异常页面一直 500控制台能看到找不到 jstl 相关类。原因项目的 pom.xml 里没引 JSTL 依赖或者引了但没加 scope。SSM 项目虽然标配了 JSTL 标签库但很多源码包切掉了这个配置。解决dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency加完后重新导入 Maven 项目再启动确认。如果你是 JDK 9 以上还会碰到 javax 包被移出的问题那就更麻烦所以我前面才反复强调用 JDK 8。4.2 数据库连接失败Access denied 或 Public Key Retrieval is not allowed现象日志里报Access denied for user rootlocalhost或者Public Key Retrieval is not allowed。原因前者是密码不对或用户名不对后者是 MySQL 8.0 默认认证插件是 caching_sha2_password老驱动在非 SSL 连接下拿不到公钥。解决密码不对就重置 MySQL 账号认证问题按第 3 章说的把驱动换成com.mysql.cj.jdbc.Driver连接串加上allowPublicKeyRetrievaltrue。顺带检查 pom.xml 里 mysql-connector-java 的版本5.1.x 对 MySQL 8.0 基本无解直接升到 8.0.x。4.3 页面跳转正常但接口报 Invalid bound statement (not found)现象页面能打开列表数据全空或 Ajax 请求返回 500控制台明确报Invalid bound statement。原因mapper 接口扫描到但对应的 XML 文件没加载最常见是 resources 目录下的 mapper XML 文件后缀是.xml但放错了目录或 namespace 写错。解决打开 target/classes 目录看有没有把 mapper XML 拷进来没有的话在 pom.xml 的 build 里加resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource /resources这段配置是把写在 java 包下的 mapper XML 一并打包很多源码包就坑在这里。4.4 JSP 页面 EL 表达式原样输出${user.name} 直接显示在页面上现象页面不报错但${...}写的东西没有解析明晃晃地显示在页面上。原因web.xml 的版本太老Servlet 2.4 以下默认不启用 EL。很多毕业设计项目 web.xml 头声明还停留在 2.3JSP 引擎就不解析表达式。解决把 web.xml 的头部标准声明改掉web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 /web-app4.5 中文乱码登录后用户名、商品名全是问号现象页面内容有的是 UTF-8有的是 GBK数据库里存进去的名字在页面上显示成???。原因三个环节的编码不一致数据库连接串、页面 contentType、Tomcat URI 编码至少要统一。按这套改一遍就能解决# jdbc.properties jdbc.url...characterEncodingutf8!-- JSP 页面头部 -- % page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Tomcat 的 server.xml 里给 Connector 加URIEncodingUTF-8。这三处全改完清空浏览器缓存重新登录验证。5. 把别人的毕设变成自己的论文、PPT 与功能改造的三个抓手5.1 论文和 PPT 怎么用结构拆解与答辩逻辑源码包里带的论文不能直接拿去交照着改结构比从零写到快得多但改法有讲究。论文动三个部分需求分析里的“用户角色”和“功能需求”要结合你实际演示的功能改数据库设计章节直接用第 2 章的表结构图重画成 ER 图测试章节必须补充你亲手跑的测试用例和截图。核心代码展示部分不要整段粘贴选关键业务链比如进货单事务那 20 行代码加注释说明设计意图这就是答辩时的亮点。PPT 按十页规划选题背景、系统架构图、功能结构图、数据库 ER 图、界面截图三页、测试结果、总结展望。注意系统架构图画 SpringMVC 的执行流程不要画成网络拓扑图评委想看的是你懂不懂框架的请求流转不是你会不会画连接线。答辩时被问最多的永远是三个问题数据库表之间什么关系、事务怎么保证一致性、为什么选 SSM 不选 Spring Boot。你按第 2 章的理解去答顺着表结构讲到父子表再讲到Transactional基本不会冷场。5.2 加一个库存预警功能从 SQL 到页面的完整改动把别人的毕设变成自己的最直接的办法是加功能。我建议加库存预警改动小、演示效果好字段都现成。商品表里已经有 warn_limit 字段只要查询时把库存量和预警值比对并标注出来。先改 Mapper 的查询 SQLSELECT p.id, p.product_name, p.product_code, s.stock_count, p.warn_limit, CASE WHEN s.stock_count p.warn_limit THEN 预警 ELSE 正常 END AS stock_status FROM product_info p LEFT JOIN stock_info s ON p.id s.product_id然后在 Controller 里加一个查询方法返回带 stock_status 的商品列表。JSP 页面在商品列表表格中加一列用c:if判断字符串status 等于“预警”时给单元格加一个红色字体样式c:if test${item.stockStatus 预警} span stylecolor:red;库存不足/span /c:if c:if test${item.stockStatus ! 预警} span库存正常/span /c:if这个功能演示时效果很直观把某个商品库存修改到预警线以下刷新页面立即标红。答辩时你可以顺带讲一讲 LEFT JOIN 和 INNER JOIN 的差别为什么用 LEFT JOIN——因为要查出所有商品哪怕它还没有库存记录。5.3 答辩前必须验证的十个功能点改完代码别直接去答辩按下面这个清单快速过一遍每项确认 OK 再划掉。序号验证点具体操作1登录与权限用 admin 登录非管理员账号登录后看不到用户管理菜单2商品增删改查新增、编辑、删除各执行一次列表刷新无异常3进货全流程新建进货单选供应商加商品库存数量同步增加4销售全流程新建销售单库存数量同步减少金额汇总正确5库存异常处理销售数量大于库存时报错提示不生成销售单6搜索与分页关键字搜索商品、供应商分页切换正常7表单校验必填项留空提交有提示且不报 5008事务回滚造一个进货单明细为空的情况观察主表是否回滚9数据统计报表页面的销售总额、进货总额与明细对得上10会话超时长时间不操作后跳转登录页第 8 项容易被忽略但评委很爱现场试。Transactional只在 RuntimeException 下回滚如果你的 Service 手动 catch 了异常没往外抛事务就失效了这个细节能拿出来讲透很加分。6. 进阶动作把销售报表导出到 Excel再看一眼事务边界答辩和项目完善阶段我强烈建议给报表页加一个导出 Excel 的功能。超市进销存系统的报表本来就适合放 Excel评委也爱看这种实用功能。用 Apache POI 实现也不复杂Controller 层加一个方法返回类型设为 void通过 HttpServletResponse 写出文件RequestMapping(/export) public void export(HttpServletResponse response) throws IOException { ListSaleOrderVO list saleOrderService.getSaleReportList(); HSSFWorkbook workbook new HSSFWorkbook(); HSSFSheet sheet workbook.createSheet(销售报表); String[] headers {单号, 商品名称, 数量, 成交价, 小计, 日期}; HSSFRow headerRow sheet.createRow(0); for (int i 0; i headers.length; i) { headerRow.createCell(i).setCellValue(headers[i]); } int rowNum 1; for (SaleOrderVO vo : list) { HSSFRow row sheet.createRow(rowNum); row.createCell(0).setCellValue(vo.getOrderNo()); row.createCell(1).setCellValue(vo.getProductName()); row.createCell(2).setCellValue(vo.getQuantity()); row.createCell(3).setCellValue(vo.getPrice()); row.createCell(4).setCellValue(vo.getSubtotal()); row.createCell(5).setCellValue(vo.getCreateTime()); } response.setContentType(application/vnd.ms-excel); response.setHeader(Content-Disposition, attachment;filenamesale_report.xls); workbook.write(response.getOutputStream()); workbook.close(); }这段代码的要点有三个文件名不要写死路径用响应头告诉浏览器下载HSSFWorkbook 对应 xls 格式数据量大时换成 XSSFWorkbook 对应 xlsxworkbook.close()一定要写POI 打开的文件句柄不释放第二次导出就会报文件被占用。导出后别忘记验证一下数字精度小数位丢失的问题在 Excel 导出上很常见POI 的数值单元格如果直接传 double显示可能变成 49.9999四舍五入格式要自己在 settCellValue 前处理好。我最早改这类毕设项目时吃过一次大亏给进货单加了导出功能兴冲冲跑起来发现导出的 Excel 全部打不开仔细一看是文件流没关Tomcat 里堆了几十个文件句柄。从那以后我每次拿到一套源码都强制先跑通主链路再改代码改完功能必须过一遍“新功能能不能正常返回、异常输入会不会抛 500、多操作几次会不会内存溢出”这三道检查才敢收工。这套习惯救了我好几次也分享给你希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询