☰
基于SSM+Maven+MySQL的仓库管理系统从搭建到实战全解析
2026/10/5 3:04:52 网站建设 项目流程

做仓库管理系统(WMS)是 JavaWeb 学习者绕不开的一个完整案例。这套基于 SSM + Maven + MySQL 的仓库管理系统,基本就是把真实企业里最常见的进销存业务,用最经典的技术栈完整实现了一遍:前端 JSP 做页面交互,SpringMVC 接请求,Spring 管对象,MyBatis 操作 MySQL 数据,Maven 统一管依赖,最后整体丢到 Tomcat 里跑。它解决的核心问题很直接:让仓库里的商品入库、出库、库存查询、供应商管理这些事,从手写 Excel 变成系统化操作。适合正在做 JavaWeb 课设或毕设的同学、想彻底搞懂 SSM 整合全流程的新人,以及需要快速搭一套内部管理系统的开发者。这篇文章不聊虚的,我把整个项目的设计思路、环境配置、核心代码逻辑和调试过程完整复盘一遍。

1. 项目到底在做什么:系统拆解与架构选择

1.1 仓库管理系统的核心业务

仓库管理系统听起来高大上,拆开看就是围绕“货”的增删改查。我在设计表结构之前,先把业务捋了一遍,至少要有这几张核心表:用户表(管理员和操作员)、商品分类表、商品信息表、供应商表、入库单表、出库单表。库存不应该单独手工维护,而是通过入库单和出库单自动推算出来,这一点非常重要。

具体到操作流程,用户登录系统后要能看到首页的库存概览:总商品数、库存总量、库存预警数量。然后可以做商品档案管理,包括新增、编辑、上下架、按名称或分类筛选。入库是核心操作,选择了供应商、填了商品和数量后,系统要同时更新商品库存并生成一条入库流水。出库则相反,但要先校验库存是否充足,不够就直接拦截。如果某个商品库存低于预设阈值,列表里就要有醒目的预警标记。

权限上我做了最简单的两层:管理员可以修改商品和查看所有报表,普通操作员只能做入库、出库和查询。这个划分不复杂,但在 JSP 页面里通过 session 判断用户角色就能实现,足够演示权限控制的思路。

1.2 为什么还用 SSM + Maven + JSP 这套组合

很多人问我为什么不直接用 Spring Boot,前端为什么不换成 Vue。我的回答是:这套仓库管理系统本来就是用来展示 JavaWeb 底层原理的,SSM 才是最合适的教材级组合。Spring 的 IOC 和 AOP 是框架的地基,SpringMVC 把请求路由和参数绑定讲得明明白白,MyBatis 让你能清楚看到 SQL 和代码的关系。换成 Spring Boot 后自动配置把细节全藏起来了,新手反而看不懂。

JSP 在这个项目里也完全够用。内部管理系统数据量不大,页面数量在二三十个左右,JSP 可以天然共享 session 中的用户信息,不需要额外做 token 传递。配合 JSTL 标签和 EL 表达式,用<c:forEach>遍历商品列表,用${sessionScope.user.username}显示当前登录人的名字,代码量比 Vue + Axios 那套要少得多。整个系统跑在 Tomcat 虚拟机上,内存占用很低,几千条商品数据下页面响应都很流畅。

Maven 的价值在依赖管理上体现得最直接。以前做 JavaWeb 项目要手动去下载各种 jar 包,不同版本的 Spring 和 MyBatis 之间经常出现兼容问题。用 Maven 之后,pom.xml 里声明依赖坐标,所有 jar 包统一走仓库下载,版本冲突有 Maven 的依赖仲裁机制处理,项目构建也统一成 compile、test、package、install 这几个标准阶段,团队协作时大家拿到的构建结果完全一致。

1.3 数据库设计的关键点

MySQL 建表有几个容易忽略的细节。第一是所有表都建议加create_time和update_time字段,虽然现在可能没用,但后面做数据统计和排查问题时非常必要。第二是库存数字和金额要用DECIMAL而不要用FLOAT,钱和数量出现浮点精度问题会非常痛苦。第三是字符集统一用utf8mb4,如果坚持用utf8,商品名里一旦出现生僻字或者 emoji 就会乱码。

我贴一段商品表的建表语句,实战里可以直接参考:

CREATE TABLE `t_goods` ( `id` INT NOT NULL AUTO_INCREMENT, `goods_code` VARCHAR(32) NOT NULL COMMENT '商品编码', `goods_name` VARCHAR(64) NOT NULL COMMENT '商品名称', `category_id` INT NOT NULL COMMENT '分类ID', `spec` VARCHAR(64) DEFAULT NULL COMMENT '规格', `stock` INT NOT NULL DEFAULT 0 COMMENT '当前库存', `min_stock` INT NOT NULL DEFAULT 10 COMMENT '库存预警阈值', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1上架 0下架', `create_time` DATETIME NOT NULL, `update_time` DATETIME DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_goods_code` (`goods_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品信息表';

库存和流水的关系我采用的是“两张表联动”设计:t_goods.stock是当前库存快照,t_stock_flow记录每一次变动。这样查询列表很快,同时又能追溯每一次出入库历史。中间用事务保证一致性,这个在第三章我会详细说。

2. 环境搭建:IDEA、Maven、MySQL、Tomcat 一次到位

2.1 Maven 安装与配置:本地仓库、阿里云镜像、JDK 版本

Maven 的安装本身不难,但配置细节能卡住一大批人。我建议下载 3.6.3 或者 3.8.8 这个版本区间,太新的 Maven(比如 4.x)对部分旧插件兼容性不好,太老的又跟 JDK 8 之后的版本有兼容问题。下载解压后,首先要配环境变量MAVEN_HOME指向解压目录,再把%MAVEN_HOME%\bin加入 PATH。命令行执行mvn -v能打印出版本号就说明装好了。

接下来是修改conf/settings.xml,这是 Maven 的核心配置文件。第一个必改项是本地仓库路径<localRepository>,默认值在用户目录下的.m2/repository,我习惯改到D:/repo/maven这种独立目录,避免系统盘膨胀。第二个必改项是阿里云镜像,否则从中央仓库下载依赖会慢到怀疑人生:

<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>

这里我要多说一句,有些同学配置完镜像还是下载报错,多半是mirrorOf写错了。*表示匹配所有仓库,central只匹配中央仓库,如果项目里还用了其他私有仓库,建议用*,!thirdparty这种排除写法,避免把公司内部的包也拦截掉。

最后是 JDK 版本。如果本地同时装了 8 和 17,Maven 默认会用JAVA_HOME指向的 JDK。SSM 项目建议统一用 JDK 8,因为 Tomcat 9 和大量老版本依赖在 JDK 8 下最稳定。在 IDEA 里指定项目 SDK 后,还要在pom.xml里加上maven.compiler.source和maven.compiler.target,否则编译出来的 class 文件版本可能不对,部署到 Tomcat 里就报UnsupportedClassVersionError。

2.2 MySQL 安装与初始化

MySQL 的安装版本选择我建议直接上 8.0 系列,社区版下载下来一路 Next 装完即可。不过有一点必须提醒:MySQL 8.0 的默认认证插件是caching_sha2_password,如果你的驱动版本是老的 5.x,连接时会报认证失败。解决办法是驱动升到mysql-connector-java8.0 以上的版本,或者在建用户时指定mysql_native_password。我在项目里用的驱动坐标是:

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>

安装完成后先启动服务,然后执行mysql_secure_installation或手动设置 root 密码。还要把默认字符集改掉。MySQL 8.0 的默认字符集已经是utf8mb4,如果你是装 5.7.44 这样的老版本,建议在my.cnf的[mysqld]段里加上:

character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci

测试连接时最容易踩的坑是 SSL 相关报错。驱动 8.0 版本默认开启 SSL 连接,如果 MySQL 服务端没有配好证书,日志里就会报SSL connection error。开发环境下最简单的方式是在 JDBC URL 里显式关闭 SSL:jdbc:mysql://localhost:3306/wms?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8。serverTimezone也是必加的,否则驱动的时区校验会报错,导致连接失败。

2.3 IDEA 中创建 SSM 工程并配置 Tomcat

在 IDEA 里创建 SSM 项目,我推荐用 Maven 的maven-archetype-webapp原型,勾选创建完成后会自动生成标准的src/main/resources目录。如果没有,手动右键src/main目录 New -> Directory 创建 resources 目录,再通过 Mark Directory as Resources Root 标记一下。

工程结构建议这样组织:src/main/java下按包分层建controller、service、mapper、entity、common,src/main/resources下放jdbc.properties、spring-mybatis.xml、spring-mvc.xml、log4j.properties,src/main/webapp/WEB-INF下放web.xml和所有 JSP 页面。

IDEA 中配置 Tomcat 有几个关键点要记牢。Run -> Edit Configurations,加一个 Tomcat Server Local,Application server 选择本地 Tomcat 9 的目录。Deployment 页签点击加号选 Artifact,选war exploded模式,这样支持 JSP 和静态资源的改后热更新。Application context(虚拟路径)我习惯设为/wms,访问首页就是http://localhost:8080/wms/。

然后在 VM options 里加一行参数,这个能让控制台里同时看到 Spring 的日志:

-Dcatalina.base="D:/apache-tomcat-9.0.xx"

如果你系统已经装了别的服务占用了 8080 端口,在 HTTP port 那里改成 8081 或 9090 都行,改完记得项目里跳转 URL 也跟着改。还有个小技巧:把 JSP 页面的isELIgnored设为false,否则页面上${}表达式不会被解析,在 JSP 顶部加上<%@ page isELIgnored="false" %>即可。

3. 核心功能实现:登录、库存管理、入库出库逻辑

3.1 SSM 三层架构与常用注解

SSM 项目的经典分层是 Controller、Service、Mapper 三层,配合 Spring 的 IOC 容器把它们串起来。Controller 层负责接收请求和返回页面,Service 层处理业务逻辑,Mapper 层通过接口定义和 XML 映射文件操作数据库。

Controller 层常用的注解有这么几个:@Controller标注类为 SpringMVC 的控制器,@RequestMapping定义访问路径,@ResponseBody表示返回 JSON 数据而不是页面名称,@RequestParam做参数绑定。Service 层常用@Service和@Transactional,前者标记业务组件让 Spring 扫描管理,后者给方法加事务。

MyBatis 这边,Mapper 接口加@Repository(或者用@MapperScan在配置类里统一扫),接口方法上可以加@Select、@Insert、@Update、@Delete注解直接写 SQL,也可以在 XML 文件里写。我做这个项目时是接口加 XML 结合的方式,简单的 CRUD 用注解,复杂一点的多表联查放到 XML 里维护。

有一点新手特别容易踩:Service 类初始化时如果发现 Mapper 为 null,先检查spring-mybatis.xml里是否配置了<mapper-scanner base-package="com.wms.mapper" />或者@MapperScan。Spring 和 MyBatis 的整合是通过SqlSessionFactoryBean加扫描器完成的,缺少这一步,容器里根本找不到 Mapper 的代理实现。

3.2 库存核心逻辑:入库、出库与事务

入库是仓库管理里最核心的操作。我在 Service 里定义了一个doInbound方法,流程是这样的:先根据商品编码查到商品记录,数据库行锁锁住这条数据,然后更新库存(stock = stock + inbound_quantity),同时插入一条入库流水。整个过程必须在同一个事务里,任何一步失败都要回滚,否则就会出现库存加了但流水没记录的脏数据。

出库的逻辑反过来,但多了一步库存校验。扣减数量不能小于 0,否则直接抛异常提示“库存不足”。我实际写的时候是这种风格:

@Transactional(rollbackFor = Exception.class) public boolean doOutbound(OutboundParam param) { Goods goods = goodsMapper.selectForUpdate(param.getGoodsId()); if (goods.getStock() < param.getQuantity()) { throw new RuntimeException("库存不足,当前剩余:" + goods.getStock()); } goods.setStock(goods.getStock() - param.getQuantity()); goodsMapper.updateStock(goods); // 插入出库流水 stockFlowMapper.insert(new StockFlow(...)); return true; }

这里有个细节:selectForUpdate对应 Mapper XML 里的SELECT ... FOR UPDATE,原地锁住这条商品记录。这样做是为了防止两个人同时对一个商品出库,各自读到旧库存,最后把库存扣成负数。虽然这个项目是单机部署,并发量不大,但写好这个习惯能保证以后换到高并发环境也不会出大问题。

事务为什么必须加rollbackFor = Exception.class?因为 Spring 默认只对RuntimeException回滚,如果方法里抛的是受检异常(比如IOException),事务不会回滚,这个问题我在实际项目里遇到过,查了很长时间才发现。SSM 配置里还需要在spring-mybatis.xml中启用事务注解驱动和事务管理器,否则@Transactional完全不生效,这也是一个经典坑位。

3.3 JSP 页面联动与信息展示

JSP 页面在这个系统里承担了全部前端职责。登录页面提交表单到/login,Controller 验证用户名密码通过后把用户对象丢进 session,然后return "redirect:/index"跳到主页。主页是一个框架页,左侧是导航菜单,右侧嵌入商品列表、入库单列表、出库单列表。

列表页面我用了c:forEach遍历 PageHelper 分页后的数据,每个商品行后面跟着“编辑”、“入库”、“出库”、“删除”四个操作按钮,这些按钮链接到不同的 Controller 路径。页面右上角展示当前登录人的信息,就是第一章提到的${sessionScope.user.username},同时我用一个 JSLT 判断显示“管理员”或“操作员”的标签:

<c:if test="${sessionScope.user.role == 1}"> <span class="badge">管理员</span> </c:if> <c:if test="${sessionScope.user.role == 2}"> <span class="badge">操作员</span> </c:if>

有同学问过 JSP 页面怎样实现进入后自动刷新一次,这个场景通常用在“入库成功后跳转到列表页”时,想确保看到最新数据。JavaScript 里写一行window.location.reload()放在 body 的 onload 里就好,或者用<meta http-equiv="refresh" content="0">。但要注意别在首页无限刷新,避免死循环。

关于图标,很多同学想在 JSP 里用 Element UI 的图标,那个是 Vue 组件库里的东西。我在这个项目里没硬套,直接用 Font Awesome 或者 Bootstrap 自带图标,JSP 引一份在线 CDN 就行,不用为了个图标把 Vue 引进来,那会让页面渲染逻辑复杂化。如果你想在传统 JSP 页面里局部用 Vue 语法,也不是不行,但和原生 JS 混在一起很容易出现 Vue 实例未初始化就调方法的情况,不建议初学者尝试。

4. 从 0 到 1 的实操复盘:部署与调试记录

4.1 首次启动最常见的几个异常

项目写完,第一次点运行,报错是家常便饭。我把自己实际遇到的几个高频异常列一下,按出现频率排序。

第一个是 404 白屏。这种问题九成是请求路径没有对上 Controller 的@RequestMapping,或者是 web.xml 里<servlet-mapping>的 url-pattern 写错。SSM 项目里DispatcherServlet要拦截.do或/,如果拦截配置不当,请求直接落入默认 Servlet,SpringMVC 根本接不到。调试办法很简单:看 IDEA 控制台有没有打印 “Mapping” 那几行日志,有的话说明请求映射注册成功,没有就去查注解路径。

第二个是启动时报Error creating bean with name 'sqlSessionFactory'。通常是数据源配置有问题,jdbc.properties 里的driver、url、username、password四项缺一不可,而且class不能打错。MySQL 8.0 一定要用com.mysql.cj.jdbc.Driver,MySQL 5.7 可以用com.mysql.jdbc.Driver,这个版本对应关系我记了很多次才记住。

第三个是 JSP 页面报 500,看堆栈发现是java.lang.NullPointerException。大概率是 EL 表达式里取了一个 null 对象的属性。比如从 session 里取用户,如果登录验证没通过直接跳转到受保护页面,session 里自然没有用户,一取值就空指针。解决思路是在IndexController里做一次 session 校验,取不到用户就重定向到登录页。

4.2 MySQL 连接异常与 SSL 坑

MySQL 连接报错是真的多。我整理了一个问题速查表,遇到时可以直接对照:

报错信息根因解决办法
Public Key Retrieval is not allowedMySQL 8.0 缓存加密模式JDBC URL 加allowPublicKeyRetrieval=true
SSL connection error驱动默认开启 SSL 校验加useSSL=false
Access denied for user用户名密码错或账号只允许 localhost核对密码,调整授权
Unknown database数据库还没建先执行CREATE DATABASE wms
Connection refusedMySQL 服务没启动启动服务,Linux 下用 systemctl 或 service
CST时区问题时区参数缺失加serverTimezone=Asia/Shanghai

这里还要单独提一个热词里看到的点:MySQL 5.7.44 官方在 5.7 系列里基本是收尾版本了,之后这个分支不再更新,后续只能升 8.0。如果你照着我这篇博文做毕设,建议第一步就装 8.0,别在 5.7 上折腾了。8.0 和 5.7 在驱动类名、密码校验、SQL 语法上都有差别,早用早踩坑,总比答辩前才发现连不上数据库强。

4.3 Maven 依赖报错与本地仓库合并问题

Maven 依赖报错是另一个高频翻车点。最常见的场景是 IDEA 里 pom.xml 的依赖坐标下方出现红色波浪线,提示Cannot resolve symbol。原因通常是网络问题导致 jar 下载不完整,或者版本号拼写错误。处理方法分三步:先检查 IDEA 的 Maven 配置是否正确指向了本地仓库和settings.xml;再执行mvn clean install看命令行里的报错信息;最后把本地仓库里对应的.lastUpdated后缀文件删掉,重新下载。

热词里有人问“我有两个 Maven 本地仓库,怎么合并”,这个我也实际处理过。假设仓库 A 和仓库 B 分别在不同的磁盘目录,最简单的方法是修改settings.xml的<localRepository>指向一个空目录,然后新建一个工程把需要合并的依赖坐标全部写进 pom.xml,执行一次mvn clean install,让 Maven 重新从远程拉取一遍。这是最干净的合并方式,不用手动拷贝文件名带一堆时间戳的目录。

如果你想保留两个仓库的离线内容,可以用 Nexus 搭一个私服,把两个仓库作为 proxy 源,开发机上统一指向私服。不过这个方案对仓库管理系统这个项目来说太重了,普通开发环境我建议直接重新拉,稳妥省事。

4.4 依赖冲突与版本仲裁

SSM 项目里依赖冲突几乎避不开,典型的是 Spring 版本不一致。比如自己加了spring-core5.3.20,但某个库又传递依赖了 4.x 的spring-context,运行时就可能出现NoSuchMethodError或者方法签名不匹配。

Maven 的依赖仲裁原则是最短路径优先,如果实在判断不了,可以执行mvn dependency:tree查看依赖树。我一直在用这个命令定位冲突,输出结果里把带omitted for conflict的依赖标出来,然后根据实际需求在 pom.xml 中通过<exclusions>排除掉多余版本。

当然,对于仓库管理系统这种项目,最稳妥的办法是统一维护一套全家桶依赖。比如 Spring 直接引入spring-webmvc这一个坐标就能带出 core、beans、context 等模块,版本号都由这个主坐标决定,不会出现 Spring 模块之间的版本漂移。MyBatis 和它的 Spring 整合包mybatis-spring也要注意版本匹配,我用的是mybatis-3.5.13配mybatis-spring-2.0.6,实测配合没问题。

5. 项目扩展方向与最终心得

这个仓库管理系统做到能跑能演示的程度,其实只完成了基础版。真正往里加东西,可以走几条路:第一,引入 Excel 导入导出,把商品批量录入和库存盘点做成 Excel 交互;第二,把权限模型从两层升级成基于角色的 RBAC,支持多角色、多菜单权限;第三,把前端从 JSP 逐步替换成 Vue + Element UI,后端提供 REST API,这算是向现代开发模式过渡的方向;第四,加上数据可视化报表,比如用 ECharts 画出月度入库出库趋势图,这个对答辩或者项目演示非常加分。

我在实际开发中的体会是,SSM 项目最大的价值不在于框架本身有多么先进,而在于它能让你把 JavaWeb 的底层脉络彻底搞清楚。每次遇到问题,你都不会像用 Spring Boot 那样只改一个配置就完事,而要深入想到 Spring 容器怎么管理的、MyBatis 的代理对象怎么生成的、Tomcat 的类加载顺序是怎样的。这些知识在面试里反而是最值钱的。

最后分享一个小技巧:整个项目开发完后,把数据库脚本、pom.xml、配置文件的修改记录都整理好,写一份简单的部署文档。这个习惯让我在换电脑、换环境、重新部署的时候省了很多时间。写文档的过程其实也是对新学的技术点做二次梳理,很多没想明白的地方,写着写着就通了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询