ARTICLE DETAIL

资讯详情

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

JSP+SSM进销存系统实战:从建库配置到库存事务与答辩演示

JSP+SSM进销存系统实战:从建库配置到库存事务与答辩演示 简介一套面向毕业设计场景的生鲜超市进销存管理系统完整项目包适合计算机、软件工程等专业学生用于毕业设计、课程设计或SSM框架开发入门参考。后台采用SSM三大框架前端页面使用JSP数据库为MySQL基于JDK1.8开发Eclipse、MyEclipse、STS、IDEA均可运行。项目功能涵盖销售出库管理、进货采购管理、员工供应商客户等基本档案管理以及商品类别编辑、商品及库存管理采购环节还能对市场监管不合格商品给出提示。整个压缩包共428个文件包括82个Java源码、82个class编译文件、77个jar依赖包、60个JSP页面、38个XML配置以及图片、CSS、JS等资源大小约35.79MB目录与代码分层清楚便于对照阅读。除项目源码外附带数据库脚本、论文文档、环境工具包及同框架项目的安装教程覆盖从环境配置、数据库导入到项目部署的关键环节已有89人学习适合毕业设计从搭建到撰写的全过程参考。1. 生鲜超市进销存管理系统这套JSPSSM毕业设计到底解决什么问题临到答辩前一周手里只有一份“jspssm生鲜超市进销存管理系统B源码含文档含教程”的压缩包这是很多应届生会遇到的真实状态。这个标题拆开看就三件事JSP做页面视图SSMSpringSpringMVCMyBatis做Java Web后端的经典组合业务域是生鲜超市的进货、销售与库存。它要解决的问题很具体——让一个没有完整项目经验的人在最短时间内从建库、配环境、启动项目一路走到改代码、讲业务、通过答辩。适合做Java Web课程设计或毕业设计的学生也适合想快速把SSM框架串起来的初学者。这篇笔记只围绕一个核心拿到这份源码之后怎么把它跑起来并且跑得明白。2. 技术选型与项目结构JSPSSM为什么还是毕业设计的安全牌2.1 三层架构落地Spring、SpringMVC、MyBatis、JSP各管哪一段很多同学看到JSP就觉得技术老但毕业设计场景下SSM反而是最稳妥的组合。Spring容器管对象和事务SpringMVC管URL到Controller的映射MyBatis管SQL与Java对象之间的转换JSP负责把数据渲染成页面。每层职责清晰答辩时老师随便挑一层问都能讲出东西。这里必须先分清几个SSM常用注解的归属这是最容易在答辩时被追问的点。Controller和RequestMapping属于SpringMVC写在类和方法上决定一个URL进来之后由谁处理Service和Repository用来标记Service与Mapper实现类让Spring容器能扫描到Autowired做依赖注入Transactional加在Service方法上保证一个业务操作里的多条SQL要么全成功要么全回滚。还有一个容易忽略的RequestParam用来绑定前端传参后面写进货销售接口时几乎每个方法都要用到。我见过的反面案例是把业务逻辑直接写在Controller里一个方法几百行最后事务失效、SQL散落各处。正确的分层顺序是Controller只接收参数并调用ServiceService里写业务规则和事务边界Mapper只做单表或关联查询JSP只做展示。这套项目的源码包里Controller、Service、Mapper三层通常是按包名分好的拿到包先看包结构能少走很多弯路。2.2 源码包里的标准目录拿到B包先看哪几个文件解压源码包后不要急着在IDEA里打开先看两个地方第一是sql建库脚本的位置一般叫init.sql或database.sql附带在doc或db目录下第二是src/main/resources里的配置文件重点找jdbc.properties、spring-mvc.xml、spring-dao.xml这三个。确认文件都在再打开项目文件能避免明明配置对却跑不起来的焦虑。典型的SSM项目目录结构是这样组织的src/main/java下按controller、service、mapper、entity或pojo四个分包src/main/resources下放MyBatis的mapper.xml、数据库连接配置、Spring配置src/main/webapp/WEB-INF下放JSP页面。如果你的源码包是这种布局那它就是纯正的Maven Web项目导入方式固定不存在玄学问题。这里有一个关键的版本判断项目的pom.xml里写的Spring版本、JDK版本、MySQL驱动版本决定了你能不能跑起来。常见组合是JDK 8配Spring 5.3.xTomcat用8.5或9.0MySQL用5.7这套组合最稳。如果包是配的Spring 6.x那JDK至少得17起步很多同学打开项目发现一堆报错根子往往出在JDK对不上。2.3 Maven依赖最小可用的pom.xml一个能跑通的最小pom.xml依赖就五组spring-webmvc、mybatis、mybatis-spring、mysql-connector-java、jstl另外javax.servlet-api要打成provided因为Tomcat自带Servlet容器。spring-jdbc和spring-tx通常会被spring-webmvc间接带过来但为了保险我一般会显式加上。dependencies !-- Spring MVC 与 Spring 核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.24/version /dependency !-- MyBatis 与 Spring 整合 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.10/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.6/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency !-- JSP 标准标签库 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- Servlet APITomcat 已提供避免冲突 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency /dependenciesmysql-connector-java的版本选择要跟着MySQL走本地数据库是5.7就用5.1.49是8.0就用8.0.33同时JDBC驱动类名也要从com.mysql.jdbc.Driver改成com.mysql.cj.jdbc.Driver。spring-mybatis这套组合在Spring 5.x和MyBatis 3.5.x下兼容性最好不要追新版本毕业设计场景下“能跑”比“够新”重要得多。3. 建库脚本与核心表设计生鲜SKU、供应商、进货单、销售单的建模细节3.1 六张表的依赖关系与设计顺序进销存系统的数据模型核心是六张表用户表、供应商表、商品表、进货单及明细、销售单及明细外加一张库存流水表。建表顺序不能乱先建无依赖的供应商表再建依赖供应商的商品表然后才是进货单和销售单最后建库存流水表。生鲜超市和普通进销存最大的区别在商品表上。生鲜商品有保质期属性同一种蔬菜不同批次的进货价可能不一样所以商品表里要单独存生产日期produce_date和保质期天数expire_days而不是只存一个分类。还有一个容易忽略的点单位不能统一用“个”生鲜里既有按斤卖的蔬菜也有按盒卖的豆腐、按袋卖的面点所以unit字段和spec字段必须留出来。另一条设计原则是主表和明细表分离。进货单只存单号、供应商、总金额、进货日期这些汇总信息具体进了哪些商品、进价多少、数量多少放在明细表里。为什么不分在一张表里因为一次进货往往是十几二十种商品如果全塞进一张表要么产生大量冗余的重复字段要么只能设计成严重的反范式结构后端写统计SQL会非常痛苦。这种主从表结构在答辩时也是重点考点值得单独准备一段话说明“为什么要拆表”。3.2 核心表结构goods与inventory_log下面这两张表是整套进销存的地基其余单据表都是围绕它们扩展的。goods表负责记录商品当前库存和预警阈值inventory_log表负责记录每一次库存变动的流水包括进货入库、销售出库、盘点调整这三种类型。CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL COMMENT 商品名称如本地生菜, category VARCHAR(32) COMMENT 分类蔬菜/水果/肉禽/水产, unit VARCHAR(8) DEFAULT 斤 COMMENT 单位斤/盒/袋, spec VARCHAR(32) COMMENT 规格如500g/份, stock INT DEFAULT 0 COMMENT 当前库存数量, warn_stock INT DEFAULT 10 COMMENT 库存预警阈值, purchase_price DECIMAL(10,2) COMMENT 最近进货价, sale_price DECIMAL(10,2) COMMENT 零售价, produce_date DATE COMMENT 生产日期, expire_days INT COMMENT 保质期天数, supplier_id INT COMMENT 供应商ID逻辑关联, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, UNIQUE KEY uk_name_spec (name, spec) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT生鲜商品表;CREATE TABLE inventory_log ( id INT PRIMARY KEY AUTO_INCREMENT, goods_id INT NOT NULL COMMENT 商品ID, type TINYINT NOT NULL COMMENT 1入库 2销售出库 3盘点调整, quantity INT NOT NULL COMMENT 正数入库负数出库, ref_no VARCHAR(32) COMMENT 关联单据号如进货单号P开头的单, operator VARCHAR(32) COMMENT 操作人, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_goods_time (goods_id, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT库存流水表;good表里我用的是逻辑关联而不是物理外键supplier_id只加普通索引。这样设计的原因在下一小节展开。注意quantity字段在inventory_log里做成有符号整数入库写正数销售出库写负数盘点调整按实际增减写正负。这样后面做日结对账时直接按type分组求和就能算出当日净变动不用区分加减方向。3.3 逻辑关联加索引外键在毕业设计里的取舍很多同学建表时习惯把外键FOREIGN KEY加上但进销存系统里我建议不建物理外键只保留逻辑关联字段和索引。原因很实际商品如果被进货单引用过物理外键会阻止删除商品导致“先删哪个表”的死锁式报错更麻烦的是演示时一旦误操作触发约束异常现场排错压力很大。逻辑关联配合普通索引已经能满足所有查询需求。-- 逻辑关联对应的索引既加速JOIN又避免物理外键带来的删除限制 ALTER TABLE goods ADD KEY idx_supplier (supplier_id); ALTER TABLE inventory_log ADD KEY idx_ref (ref_no);索引的作用这里要说清楚按商品ID查库存流水、按供应商查商品列表、按单据号反查明细这三个高频操作用到这三处索引。不过也别产生误会索引不是越多越好像inventory_log这种流水表每天增长几百条数据只要有goods_id和create_time的联合索引就完全够用。再加多余索引只会拖慢插入速度。建表阶段留着这两个索引等到第6章做库存对账SQL时你会体会到索引带来的查询速度差异。4. 从B源码到可运行IDEATomcatMySQL的启动流程与关键配置4.1 启动前核对JDK、Tomcat、MySQL版本搭配拿到B源码后最忌讳的就是直接双击pom.xml打开等着IDEA自动配好一切。我一般会先花五分钟核对本地环境否则后面每一个报错都要翻半天。最省心的搭配是JDK 8 Tomcat 8.5 MySQL 5.7 Maven 3.6这个组合下Spring 5.x和mysql-connector 5.1.x都能原生兼容。下面这个表格列的是这几年做过类似项目后总结出来的稳定组合照着配基本不用踩版本坑。组件推荐版本说明JDK1.8SSM项目编译目标普遍是JDK 8Maven3.6.3依赖下载稳定和IDEA内置Maven兼容Tomcat8.5.x 或 9.0解压版即可IDEA只需指向目录MySQL5.7兼容性最好字符集选utf8mb4IDEA2021以上自带Tomcat集成配置方便版本确认完再做一件事打开pom.xml确认打包方式为war而不是jar。SSM项目最终要部署到Tomcat的webapps目录只有war包才能被Tomcat识别。如果pom.xml里没写packaging标签默认就是jar这会让你在IDEA里部署Tomcat时找不到可部署的artifact。补一行war的packaging声明就行。4.2 数据库导入与jdbc连接参数在MySQL里建库并导入源码包中的.sql脚本。这里有个容易踩的地方建库时要在CREATE DATABASE语句里明确指定utf8mb4字符集否则导入脚本后中文字段注释全变乱码。命令如下mysql -uroot -p CREATE DATABASE fresh_market DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE fresh_market; SOURCE D:/path/to/init.sql;导入成功后在控制台执行SHOW TABLES如果能看到goods、supplier、purchase_order这些表名说明脚本执行成功。接着打开src/main/resources/jdbc.properties这里写的是数据库连接参数和本地的MySQL账号密码一一对应jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/fresh_market?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456连接串里那四个参数是血泪经验的产物。useUnicode和characterEncodingutf8控制中文字符不乱码useSSLfalse避免MySQL 5.7和驱动之间出现SSL握手告警serverTimezoneAsia/Shanghai解决时间字段差八小时的问题。password改成你自己MySQL的密码用户名如果不用root也要同步改。4.3 Spring与SpringMVC配置四个必须改的地方SSM项目的Spring配置一般拆成三个文件spring-base.xml管数据源和事务spring-mvc.xml管Controller扫描和视图解析mybatis-config.xml管Mapper扫描。下面这段是spring-mvc.xml里最核心的视图解析器配置!-- 视图解析器把Controller返回的字符串映射到/WEB-INF/views/下的jsp文件 -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views/ / property namesuffix value.jsp / /bean必须确认四个地方和你的项目实际路径一致。第一prefix指向的/WEB-INF/views/目录要真实存在如果源码包把JSP放在/WEB-INF/jsp/下这里就必须改成jsp/否则Controller返回字符串后页面直接404。第二组件扫描包名要覆盖到Controller所在的包。第三数据源里的driver、url、username、password四要素要和jdbc.properties对得上。第四MyBatis的mapper-locations要指向resources里的mapper.xml目录默认是classpath:mapper/*.xml。配置的检查方法很简单IDEA里CtrlShiftF全局搜InternalResourceViewResolver看它旁边的目录结构是否真实存在。因为源码包在不同人手里转手过很多次目录结构被改动是常态view解析器是第一个坑也是最好查的坑。4.4 IDEA部署Tomcatartifact里缺lib是404的元凶环境全部配好后,在IDEA里部署Tomcat还有最后一道坎artifact缺依赖库。很多项目在源码里能编译通过一启动Tomcat就报ClassNotFoundException或404九成是Tomcat运行时的artifact里没有包含Maven的lib目录。按这个顺序操作File → Project Structure → Artifacts → 选中你的web项目artifact → 在Output Layout标签页右侧Available Elements里找到“库元素”并双击加入lib。然后打开Run → Edit Configurations → Tomcat Server → Local在Deployment标签页里把带有exploded的artifact加进去。启动后浏览器地址栏访问http://localhost:8080/项目名/这里的项目名对应IDEA里Deployment配置的Application context。提示启动报错先看Tomcat Localhost Log和Catalina Log别先在代码里翻。大部分启动类问题日志里都写了具体原因运行日志比DEBUG断点更先给你答案。5. 进销存核心业务实现与3个高频踩坑库存流水、销售出库、乱码与报错排查5.1 进货入库一张主表加一张明细加一条库存流水进货入库是整个系统里最先写的业务因为它逻辑最直白创建进货单、写进货明细、更新商品库存、记一条入库流水。四件事必须在一个事务里完成否则会出现“单据建了但库存没加”的脏数据。下面这段是核心Service方法Service public class PurchaseService { Autowired private PurchaseOrderMapper orderMapper; Autowired private PurchaseDetailMapper detailMapper; Autowired private GoodsMapper goodsMapper; Autowired private InventoryLogMapper logMapper; Transactional(rollbackFor Exception.class) public int createPurchase(PurchaseOrderVO vo) { // 1. 生成进货单号P 当前时间戳保证全局唯一 String orderNo P new SimpleDateFormat(yyyyMMddHHmmss).format(new Date()); vo.setOrderNo(orderNo); orderMapper.insert(vo); // 2. 逐条写入明细并同步增加goods表中的库存 for (PurchaseDetail item : vo.getItems()) { item.setOrderNo(orderNo); detailMapper.insert(item); goodsMapper.increaseStock(item.getGoodsId(), item.getQuantity()); // 3. 每一条明细对应一条入库流水正数入库 InventoryLog log new InventoryLog(); log.setGoodsId(item.getGoodsId()); log.setType(1); log.setQuantity(item.getQuantity()); log.setRefNo(orderNo); logMapper.insert(log); } return 1; } }这段代码的要点有三个。Transactional(rollbackFor Exception.class)把事务边界框住任何一条SQL抛异常前面的insert和update全部回滚这是数据一致性的底线。goodsMapper.increaseStock是一条带计算的UPDATE语句UPDATE goods SET stock stock #{quantity} WHERE id #{goodsId}不要去应用层先查库存再加再写回那样并发时会丢更新。流水表里的quantity记为正数入库对库存是增加这个方向语义要和后面销售出库保持统一。5.2 销售出库与防超卖用条件UPDATE解决并发问题销售出库是进销存里最容易出问题的环节问题集中在超卖。想象这样一个场景收银台同时有两笔订单购买同一种菜两个请求同时读到库存为10各自扣减5最终库存变成5而不是0多卖出去5份。解决思路是在SQL层面做原子判断而不是在Java代码里先查再算。Service public class SaleService { Autowired private GoodsMapper goodsMapper; Autowired private SalesOrderMapper salesOrderMapper; Autowired private InventoryLogMapper logMapper; Transactional(rollbackFor Exception.class) public int createSale(SalesOrderVO vo) { // 1. 条件扣库存只有库存充足时才扣减返回影响行数 int rows goodsMapper.decreaseStockIfEnough(vo.getGoodsId(), vo.getQuantity()); if (rows 0) { throw new RuntimeException(库存不足或商品已下架); } // 2. 写销售单 salesOrderMapper.insert(vo); // 3. 记出库流水数量写成负数 InventoryLog log new InventoryLog(); log.setGoodsId(vo.getGoodsId()); log.setType(2); log.setQuantity(-vo.getQuantity()); log.setRefNo(vo.getOrderNo()); logMapper.insert(log); return rows; } }对应的MyBatis映射里decreaseStockIfEnough的UPDATE语句是关键所在update iddecreaseStockIfEnough UPDATE goods SET stock stock - #{quantity} WHERE id #{goodsId} AND stock #{quantity} /update这条SQL利用UPDATE的行锁机制同一时刻只有一个事务能修改同一行库存而WHERE stock #{quantity}这道条件让库存不足时影响行数为0Java层拿到0就抛出异常回滚整个事务。加上Transactional后销售单和库存扣减永远保持一致这是最可靠也最容易解释的实现思路答辩时把这个逻辑讲清楚老师基本不会再追问并发问题。5.3 库存预警与JSP页面刷新低于阈值商品列表库存预警的逻辑不复杂但要做到“加载完自动刷新”涉及一点JSP页面细节。预警查询的核心是找出当前库存低于预警阈值的商品SQL方向如下SELECT id, name, category, unit, stock, warn_stock FROM goods WHERE status 1 AND stock warn_stock ORDER BY (stock - warn_stock) ASC;ORDER BY (stock - warn_stock) ASC把缺货最严重的商品排在前面这样页面一打开首先看到的是最急需补货的项。在Controller里查完塞进ModelAndViewJSP用c:forEach循环渲染成表格即可。关于刷新逻辑常见的需求是“页面加载完成后自动刷新一次让最新库存数据落地”。这个在JSP里不需要写JS定时器直接用meta标签就能实现在JSP页面head区域加一行meta http-equivrefresh content30表示每30秒刷新一次页面。这种方式对进销存的库存看板很适用代码简单且成功答辩时能讲出“实时库存监控”的设计意图。如果需要登录后跳转刷新也可以配合Servlet的response.sendRedirect实现。5.4 三个高频踩坑现象、原因、解决这里把启动和运行阶段最常见的三个问题写下来覆盖的范围比较广但确实是我做过多个SSM项目后筛选出的高发故障按“现象 → 原因 → 解决”的顺序排好。第一个坑是Tomcat启动后页面404。现象IDEA启动Tomcat不报错浏览器访问项目首页却是404。原因大概率是artifact没有打包libController类运行时根本加载不到Spring的jar包。解决方法是回到4.4小节的Artifacts配置把Maven依赖双击引入Output Layout。这个坑在转手的源码包里特别常见因为每台电脑的本地Maven仓库路径不一样IDEA自动生成的artifact经常是残缺的。第二个坑是数据库中文乱码。现象页面显示商品名称全部变成问号。原因链路有两条一是建库时charset不是utf8mb4二是jdbc连接串缺characterEncodingutf8。解决方法是两处都要改数据库用ALTER TABLE goods CONVERT TO CHARACTER SET utf8mb4修复存量数据连接串补上useUnicodetruecharacterEncodingutf8重启Tomcat后乱码消失。注意JSP文件顶部的pageEncoding也要统一成UTF-8三处一致才能彻底根治。第三个坑是库存数据对不上。现象页面显示销售成功但库存表里数量没变或者出现负库存。原因基本是两种情况忘了加Transactional导致部分SQL没执行成功或者代码里用了先查再扣的写法导致并发覆盖。解决方法是给Service方法补上Transactional并把扣库存改成5.2节的条件UPDATE。这里没有捷径必须在写完每个库存相关方法后都自查一遍事务注解。6. 库存准确率验证与答辩演示一个低成本的自查方法进销存系统做到能增删改查只是及格线答辩时能不能拿出“数据一致性验证”的证据才是拉开差距的地方。这里分享一个不需要写复杂测试框架的验证方法日结对账法。原理是库存的变动守恒——期初库存加当日入库减当日出库应当等于期末库存。这个方法用一条SQL就能完成验证。-- 假设2024-05-20为盘点日统计当日每种商品的出库总量 SELECT goods_id, SUM(quantity) AS sale_qty FROM inventory_log WHERE type 2 AND create_time 2024-05-20 00:00:00 AND create_time 2024-05-21 00:00:00 GROUP BY goods_id;拿到这份出库汇总后再查一下goods表里前一天的系统库存快照手动算一遍“昨日库存 - 今日销售 今日进货”和今天的实际库存对比。如果两边一致说明事务和流水逻辑是闭环的如果不一致按这个顺序排查先看inventory_log里有没有缺漏的流水再看是不是有盘点调整没记账最后看是不是有人直接改库而不是通过单据操作。这套排查顺序能帮你快速定位到具体是哪个环节断了。答辩演示时可以把对账结果截个图放进PPT配一句话系统通过库存流水表记录每一笔出入库配合事务控制保证日结数据一致。这句话比“我的系统功能很全”有说服力得多因为它证明你不只会调用框架还理解业务闭环。做这类SSM毕业设计项目我的习惯是先跑通最小闭环再从库的设计上去抠边角。当年我自己就是因为没做日结验证答辩现场演示连续销售同一商品时库存变成负数被追问到差点翻车。后来我把这个对账方法写进项目的运维文档里成了每次改动库存逻辑后的固定动作。希望这些经验和踩坑记录能帮你把这个项目做得又快又稳少走那一段我走过的弯路。本文还有配套的精品资源点击获取
返回列表