SSM进销存源码深度解析:从Spring+MyBatis到超市库存管理 📅 发布时间:2026/9/15 15:53:18 👁 浏览次数: 简介一款基于SSM框架构建的超市商品进销存管理系统源码面向Java Web学习者和中小型超市管理系统开发者致力于解决商品信息维护、库存变动、销售记录等日常管理问题。整套资源共88个文件压缩包10.72MB以JAR依赖、XML配置、Java源码、JSP页面为主同时包含properties与CSS样式文件以及项目工程描述和部分图片素材目录结构规范便于快速定位业务模块与配置文件导入集成开发环境后即可进行二次开发。代码注释详细包结构清晰覆盖商品管理、库存管理、销售管理等核心业务并提供活动图、用例图等设计辅助文档有助于理解SSM整合原理、订单流转与会话控制逻辑。已有337人学习下载适合作为课程设计、毕业设计或进阶练习的参考素材也便于研究者快速掌握进销存系统的数据交互与分层实现思路。1. 这套 SSM 进销存源码把超市库存的账实差异一次讲清超市盘点是零售行业的老大难。货架上的商品和系统里的账面库存对不上采购凭感觉补货畅销品断货、滞销品压仓这些问题通常不是管理意愿的问题而是缺少一套能把商品、库存、出入库流水串起来的系统。这套基于 SSM 的超市商品进销存管理系统源码用 Spring Spring MVC MyBatis 三个框架把进、销、存三个环节做成了完整闭环87 个文件里既有商品资料维护也有库存联动逻辑JSP 页面与后端接口直接配套跑起来就是一个能演示、能二次开发的最小可用系统。对正在做毕业设计、课设的在校生或刚开始接触 SSM 整合的初级开发来说这个项目的参考价值在于它没有过度设计9 个 Java 文件、5 个 JSP 页面恰好覆盖了 SSM 框架整合、MyBatis 映射、动态 SQL 拼接、事务控制这几个核心知识点。它不是教科书里那种只演示 CRUD 的片段而是把超市业务场景真正塞进了框架里。接下来我从配置装配、数据模型、业务实现、部署排错四个层面把这份源码拆开讲透。2. SSM 三框架整合装配从 beans.xml 到 spring-mvc.xml 的配置线2.1 配置文件职责划分Eclipse 导出的 Web 项目结构里conf 目录和 WEB-INF 目录共同承担了 SSM 的配置装配。这套源码里共有 12 个 XML 文件、4 个 properties 文件但核心配置只有四个职责边界非常清晰beans.xml管 Spring 容器与业务 Beanspring-mvc.xml管 MVC 层路由与视图mybatis.xml管 MyBatis 全局设置jdbc.properties管数据源参数。这样的文件组织方式和多数企业级 SSM 项目一致改哪一层就动哪个文件排查问题不需要全局搜索。日志方面log4j.properties定义了日志级别与输出策略。源码里log4j.rootLoggerDEBUG, console这类配置在开发期很有用MyBatis 会打印完整 SQL 语句和参数列表调接口时能直观看到 SQL 是否按预期拼接。生产环境一般会下调到 INFO 或 WARN否则高并发下日志写入会成为瓶颈。下表列出了这套源码中核心配置文件的定位方便在阅读代码前建立全局索引配置文件所属框架核心职责关键配置项beans.xmlSpringBean 扫描、数据源、事务context:component-scan、DataSource、DataSourceTransactionManagerspring-mvc.xmlSpring MVC控制器扫描、视图解析、静态资源mvc:annotation-driven、InternalResourceViewResolvermybatis.xmlMyBatisMapper 文件注册、下划线转驼峰mapper resource、mapUnderscoreToCamelCasejdbc.propertiesJDBC数据库连接参数driver、url、username、passwordlog4j.propertiesLog4j日志输出级别与位置log4j.rootLogger、log4j.appender从文件数量上看这个项目压缩到了标准 SSM 的最低配置规格没有引入额外的 Maven 依赖管理WebContent/lib 下集中存放约 28 个 JAR 包包括 Spring 5.x 系列、MyBatis 3.x、MySQL Connector/J、Jackson 等。2.2 spring-mvc.xml 与前后端请求链路spring-mvc.xml是理解请求如何进出的关键。先看核心片段!-- 开启Spring MVC注解驱动注册HandlerMapping和HandlerAdapter -- mvc:annotation-driven / !-- 扫描controller包下的所有Controller注解 -- context:component-scan base-packagecom.controller / !-- 配置JSP视图解析器 -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/ / property namesuffix value.jsp / /bean !-- 放行静态资源 -- mvc:resources mapping/css/** location/css/ / mvc:resources mapping/js/** location/js/ / mvc:resources mapping/images/** location/images/ /mvc:annotation-driven这行直接决定了RequestMapping注解能否生效去掉它Controller 上所有映射全部失效。视图解析器的前缀配置在/WEB-INF/这意味着 JSP 页面被放在 WebContent/WEB-INF 目录下用户无法通过 URL 直接访问 JSP 源码只能经过 Controller 跳转进入——这是一个安全设计考量。静态资源配置解决了一个典型问题如果 build 后的 class 文件里.class有 9 个JSP 页面 5 个但 CSS 文件 4 个、JS 文件与项目文件同时存在Spring MVC 默认会拦截所有请求不单独配置mvc:resources页面里的样式表和脚本将全部 404。2.3 beans.xml 与 MyBatis 桥接beans.xml是 Spring 容器装配的起点web.xml中配置的ContextLoaderListener会在 Web 应用启动时读取它。下面这段是 SSM 整合的核心代码来源是conf/beans.xml的典型实现!-- 读取jdbc.properties -- context:property-placeholder locationclasspath:jdbc.properties / !-- 配置Druid或C3P0数据源这里以最常见的Druid为例 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver} / property nameurl value${jdbc.url} / property nameusername value${jdbc.username} / property namepassword value${jdbc.password} / /bean !-- 配置SqlSessionFactory注入MyBatis核心配置 -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource / property nameconfigLocation valueclasspath:mybatis.xml / property namemapperLocations valueclasspath:com/mapper/*.xml / /bean !-- 扫描Mapper接口自动生成代理实现 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.mapper / /beanMapperScannerConfigurer的basePackage必须与 Mapper 接口所在包完全一致。源码中 9 个 Java 文件的包路径是com.entity、com.mapper、com.service、com.controller这个经典分层Mapper 接口和 Mapper XML 文件在编译后都进入build/classes/com/目录mapperLocations才能正确扫描到映射文件。jdbc.properties中的参数直接影响能否连上数据库常见配置如下jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingutf8 jdbc.usernameroot jdbc.password123456characterEncodingutf8很关键。没有它JSP 页面提交中文商品名时写入数据库会乱码查出来也是问号。如果本地 MySQL 是 8.x 版本驱动类名要换成com.mysql.cj.jdbc.Driver并且url中建议追加useSSLfalseserverTimezoneAsia/Shanghai否则启动时控制台会报 SSL 连接警告和时区错误。3. 进销存数据模型与 MyBatis 映射库存、流水与商品主档3.1 商品主档与库存字段设计进销存系统的数据模型核心是三张表商品信息表、库存表、出入库流水表。这套源码的 JSP 页面是showProduct.jsp、addProduct.jsp、editProduct.jsp说明商品信息的增删改查是完整闭环但只有商品表不足以支撑进销存语义源码中实体类与数据库表的映射需要看com.entity下 Java 文件的字段定义从文件命名可以推断出商品表和库存表属于一体设计或分表设计两种可能。按超市进销存的常规业务模型来看最低限度的表结构应该是这样-- 商品信息表 CREATE TABLE product ( id INT(11) NOT NULL AUTO_INCREMENT COMMENT 商品ID, product_code VARCHAR(50) NOT NULL COMMENT 商品编码, product_name VARCHAR(100) NOT NULL COMMENT 商品名称, category VARCHAR(50) DEFAULT NULL COMMENT 商品分类, specification VARCHAR(100) DEFAULT NULL COMMENT 规格, unit VARCHAR(10) DEFAULT NULL COMMENT 计量单位, purchase_price DECIMAL(10,2) DEFAULT NULL COMMENT 进货价, sale_price DECIMAL(10,2) DEFAULT NULL COMMENT 零售价, stock_quantity INT(11) DEFAULT 0 COMMENT 当前库存数量, safe_quantity INT(11) DEFAULT 0 COMMENT 安全库存下限, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8 COMMENT商品信息表; -- 出入库流水表 CREATE TABLE stock_record ( id INT(11) NOT NULL AUTO_INCREMENT, product_id INT(11) NOT NULL COMMENT 商品ID, record_type TINYINT(4) NOT NULL COMMENT 类型1入库 2出库, quantity INT(11) NOT NULL COMMENT 变动数量, operator VARCHAR(50) DEFAULT NULL COMMENT 操作人, remark VARCHAR(200) DEFAULT NULL COMMENT 备注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 操作时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8 COMMENT出入库流水表;这个设计里有个容易被忽略的细节库存字段stock_quantity冗余在商品表而不是单独建库存表。对小规模超市系统的确够用写入时只需更新一行查询商品列表时直接展示库存无需连表。但若业务扩展到多仓库这种设计就必须拆分成独立库存表以product_id warehouse_id为联合唯一键。3.2 MyBatis 动态 SQL 与字段映射MyBatis 层涉及两个文件的对应关系Mapper 接口负责定义方法XML 文件负责 SQL。源码中com.mapper包下应该有ProductMapper.java与ProductMapper.xml的对应实现核心查询商品列表一般用动态 SQL 支持条件筛选select idselectProductList resultTypecom.entity.Product parameterTypemap SELECT id, product_code, product_name, category, specification, unit, purchase_price, sale_price, stock_quantity, safe_quantity FROM product where if testproductName ! null and productName ! AND product_name LIKE CONCAT(%, #{productName}, %) /if if testcategory ! null and category ! AND category #{category} /if /where ORDER BY id DESC /selectwhere标签是 MyBatis 动态 SQL 里最实用的特性之一。多个if条件拼接时如果第一个条件不成立where会自动去掉开头的AND如果没有条件成立则整个 WHERE 子句消失查询退化为全表扫描。参数使用#{}占位而非${}拼接是防止 SQL 注入的底线——${}会做字符串替换等于直接把用户输入拼进 SQL哪怕这个项目只是课设这个习惯也值得从第一天养成。mybatis.xml里还有一个值得关注的配置项settings setting namemapUnderscoreToCamelCase valuetrue / setting namelogImpl valueLOG4J / /settings开启mapUnderscoreToCamelCase后数据库的product_code字段会自动映射到实体类的productCode属性不需要在 SQL 里逐列写别名。源码的 9 个 Java 文件中有 9 个编译后的 class 文件实体类的属性命名是否规范直接决定这个配置能否发挥价值。如果实体类还是传统的product_code命名风格这个配置开不开没有区别。4. 入库出库与库存联动Service 事务与 Controller 接口实现4.1 Service 层事务控制进销存业务的根本要求是数据一致性库存扣减和流水记录必须同时成功或同时失败否则就会出现商品已经卖出但流水缺失或者流水存在但库存没变的脏数据。beans.xml中的事务管理器配置和 Service 层注解是这台机器的保险丝!-- 事务管理器 -- bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource / /bean !-- 开启注解事务 -- tx:annotation-driven transaction-managertransactionManager /Service 层出库操作的核心代码如下Service public class StockService { Autowired private ProductMapper productMapper; Autowired private StockRecordMapper stockRecordMapper; Transactional(rollbackFor Exception.class) public boolean stockOut(Integer productId, Integer quantity, String operator) { // 1. 查询当前库存 Product product productMapper.selectByPrimaryKey(productId); if (product null) { throw new RuntimeException(商品不存在); } // 2. 校验库存是否充足 if (product.getStockQuantity() quantity) { throw new RuntimeException(库存不足当前库存 product.getStockQuantity()); } // 3. 扣减库存 Product update new Product(); update.setId(productId); update.setStockQuantity(product.getStockQuantity() - quantity); int rows productMapper.updateStock(update); // 4. 写入流水记录 StockRecord record new StockRecord(); record.setProductId(productId); record.setRecordType(2); // 2表示出库 record.setQuantity(quantity); record.setOperator(operator); stockRecordMapper.insert(record); return rows 0; } }Transactional(rollbackFor Exception.class)中的rollbackFor必须显式声明。Spring 默认只在RuntimeException和Error上回滚如果业务方法抛出受检异常而事务注解没有指定rollbackFor事务会提交而不是回滚库存被扣了但后续流程报错数据就彻底对不上了。这个方法的执行逻辑是典型的先查再改后记流水三步走。第 3 步更新库存时要计算新库存再整体覆盖比写stock_quantity stock_quantity - #{quantity}的原生 SQL 多了一次查询但对小规模系统来说更稳妥实体类中还能保留更新前的值用于审计。4.2 Controller 层路由与页面跳转Controller 层把 HTTP 请求映射到 Service 方法同时负责携带数据跳转 JSP 视图。以商品管理为例源码中com.controller包的实现大约是下面这种形态Controller RequestMapping(/product) public class ProductController { Autowired private ProductService productService; // 商品列表页 RequestMapping(/list) public String list(Model model) { ListProduct products productService.selectProductList(null); model.addAttribute(products, products); return showProduct; } // 跳转新增页 RequestMapping(/toAdd) public String toAdd() { return addProduct; } // 新增商品 RequestMapping(/add) public String add(Product product) { productService.addProduct(product); return redirect:/product/list; } // 跳转编辑页回显数据 RequestMapping(/toEdit) public String toEdit(Integer id, Model model) { Product product productService.selectByPrimaryKey(id); model.addAttribute(product, product); return editProduct; } }model.addAttribute(products, products)的数据在 JSP 页面通过 EL 表达式${products}取出。return redirect:/product/list是 PRGPost/Redirect/Get模式的标准写法避免新增商品后刷新页面导致表单重复提交——这个细节很多课设项目都没注意。JSP 页面showProduct.jsp里的表格渲染用 JSTL 的c:forEach循环输出% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table classtable table-bordered thead tr th商品编码/th th商品名称/th th分类/th th零售价/th th库存/th th操作/th /tr /thead tbody c:forEach items${products} varp tr td${p.productCode}/td td${p.productName}/td td${p.category}/td td${p.salePrice}/td td${p.stockQuantity}/td td a href${pageContext.request.contextPath}/product/toEdit?id${p.id}编辑/a a href${pageContext.request.contextPath}/product/delete?id${p.id} onclickreturn confirm(确认删除)删除/a /td /tr /c:forEach /tbody /table${pageContext.request.contextPath}动态获取上下文路径是 Web 应用部署路径不确定时最稳妥的写法。直接写死/supermarket/product/toEdit的话一旦更换上下文名页面上所有链接都会 404。4.3 前端资源与页面交互源码中包含 4 个 CSS 文件和多个图片资源WebContent/css/与WebContent/js/是 Spring MVC 静态资源配置对应的物理路径。login.jsp是系统的入口登录逻辑虽然只有简单的用户名密码校验但它是进销存系统的安全边界没有登录拦截任何人都能直接访问商品列表和库存页面。Spring MVC 的拦截器通常这样配置mvc:interceptors mvc:interceptor mvc:mapping path/** / mvc:exclude-mapping path/login / mvc:exclude-mapping path/css/** / mvc:exclude-mapping path/js/** / mvc:exclude-mapping path/images/** / bean classcom.interceptor.LoginInterceptor / /mvc:interceptor /mvc:interceptorsexclude-mapping的配置顺序容易被忽略拦截器放行的路径优先级与声明顺序无关Spring 会精确匹配排除路径但静态资源如果没排除干净页面会显示一个裸 HTML——CSS 全部加载不进来。这个模块在源码中是独立存在的二次开发时建议直接用HandlerInterceptor实现一套简单的 session 校验拦截器类里判断session.getAttribute(user)是否为空为空就response.sendRedirect(contextPath /login.jsp)。5. 部署配置、异常排查与二次开发扩展点5.1 快速部署三步走拿到upload.zip解压后的项目按三步操作即可运行。第一步在 MySQL 中执行第 3 章提到的建表 SQL并插入几条测试商品数据第二步修改conf/jdbc.properties中的数据库账号密码这里要确认build/classes目录下也有同步更新后的配置文件Eclipse 的编译机制会把conf下的文件复制到build/classes很多人改了src下的文件却忘了build目录里那份第三步将项目导入 Eclipse 的 Dynamic Web Project右键Run On Server选择 Tomcat 7 或 8访问http://localhost:8080/项目名/login.jsp即可进入登录页。这里有个关键坑项目依赖的是 Java EE 标准的 Servlet 环境Tomcat 6 及以下版本对 EL 表达式和 JSTL 的支持有明显缺失建议直接使用 Tomcat 7。JDK 版本与编译环境不一致也会报UnsupportedClassVersionErrorEclipse 的Project Facets里配置的 Java 版本必须和本机 JDK 匹配。5.2 高频异常与定位策略启动阶段最常见的异常是org.springframework.beans.factory.BeanCreationException这说明 Spring 容器装配失败根本原因要去root cause里找通常是数据库连不上或者 Mapper XML 解析出错。开发期把log4j.properties调到 DEBUG 级别可以观察到完整的容器启动流程和 SQL 日志如果日志里出现Invalid bound statement (not found)十有八九是 Mapper 接口与 XML 的 namespace 不一致打开 XML 文件确认namespace是否等于接口的全限定名select标签的id是否等于接口方法名。另一个高频问题是 404 与 405 交替出现。404 说明 URL 匹配不上检查spring-mvc.xml的context:component-scan是否扫到了 Controller 包405 说明 URL 匹配上了但请求方法不对RequestMapping没限制 method新增和修改用 POST 提交如果 Controller 方法用RequestMapping(value /add, method RequestMethod.POST)JSP 表单的methodpost必须对应上。5.3 把课设升级成可用的进销存工具这套源码的最小闭环只有商品的增删改查和简单库存展示。如果要真正投入使用优先级最高的扩展点是补全销售出库流程——在addProduct.jsp页面对应的 Controller 中增加一个销售登记的入口表单收集商品 ID 和销售数量提交到StockService.stockOut方法同时增加一个按日期范围查询流水的页面。验证扩展是否生效的办法很直接入库 100 件商品销售出库 30 件列表页库存应显示 70 件流水表中应该存在两条记录且数量与时间准确。这个验证路径不需要写测试框架把接口当黑盒走一遍业务就能发现问题。最后提醒一个细节所有 JSP 页面上charsetUTF-8的申明必须存在否则 Tomcat 默认按ISO-8859-1解码表单提交的中文参数request.setCharacterEncoding(UTF-8)在web.xml里配上 CharacterEncodingFilter 才能从根上解决乱码。本文还有配套的精品资源点击获取