简介:一套基于SSM框架的超市管理系统完整源码,适合Java Web初学者在真实业务场景中学习企业级分层开发思路,也适合开发者作为课程设计或毕业设计的基础项目。压缩包内共有一千二百二十二个文件,整体约十九兆大小,包含大量Java源文件、JSP页面、HTML页面、CSS样式、JavaScript脚本、XML配置以及SQL数据库脚本,前后端代码与数据库初始化语句齐全,可直接导入开发工具运行调试,便于快速搭建并启动项目。系统围绕超市日常运营,实现了商品管理、库存监控、订单处理、客户维护和收银结算等功能模块,清晰体现控制层、服务层与持久层之间的协作关系;持久层采用映射配置完成数据库操作,控制层负责请求转发与响应,整体结构完整且易于扩展。通过阅读源码,可理解SSM框架整合方式、项目目录结构、依赖配置以及典型业务逻辑写法,也能掌握数据库脚本执行流程,是课程设计或框架实战的实用参考材料。目前已有二百三十六人浏览学习。
1. SSM 超市管理系统源码:先看懂包里有什么再动手
解压带「ssm项目源码」字样的压缩包,里面通常是一套 Spring + SpringMVC + MyBatis 整合的 Java Web 工程,外加一个 .sql 结尾的 MySQL 数据库脚本。超市管理系统对应商品、库存、销售、会员这类进销存业务,本质是几张业务表配上增删改查页面。
不少人拿到项目先启动 Tomcat,却报 Table doesn't exist 或 404。原因很统一:脚本没导入,或 datasource 的库名、密码与本地 MySQL 对不上。能不能跑起来,取决于 .sql 有没有正确落库。
下面按动手顺序拆:先导数据库脚本,再对准 ssm 三层配置,接着 IDEA + Tomcat 部署排错,最后给一套扩表改模块的方法,适合毕业设计和接手老工程的人。
2. 导入 mysql 数据库脚本:把超市系统的表和演示数据落库
压缩包里那个 .sql 文件是整套系统最容易忽略、也最影响成败的部分。它一般承担三件事:建库、建表、灌演示数据。SSM 项目没有自动建表能力,MyBatis 只负责读写,不会帮你 CREATE TABLE,所以脚本没导入,后面所有查询都会报 Table 'supermarket.tb_product' doesn't exist。先搞清楚脚本内容,再决定导入方式。
2.1 打开脚本先看三样东西
用普通文本编辑器打开 .sql,不要一上来就双击执行。重点看三样内容:开头有没有 CREATE DATABASE 和 USE 语句,有的话不用手动建库,没有则需要自己先建库再导入;建表语句的 ENGINE 和 CHARSET,老项目常见 MyISAM 配 latin1,中文乱码大多是从这一步埋下的;INSERT 语句的规模,决定导入时要等多久。另外留意脚本里有没有 CREATE PROCEDURE,如果含存储过程,导入账号还需要有 CREATE ROUTINE 权限。
一段典型脚本的开头长这样:
-- 脚本头部的常见写法:先建库,再切库 CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE supermarket; SET NAMES utf8mb4; DROP TABLE IF EXISTS tb_product; CREATE TABLE tb_product ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '商品ID', product_name VARCHAR(64) NOT NULL COMMENT '商品名称', category_id INT COMMENT '分类ID', sale_price DECIMAL(10, 2) DEFAULT 0 COMMENT '售价', stock INT DEFAULT 0 COMMENT '当前库存', status TINYINT DEFAULT 1 COMMENT '1上架 0下架' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';这里 IF NOT EXISTS 表示脚本可以重复执行,失败重导时不用先把旧库删掉;SET NAMES 指定当前客户端连接的传输编码,不写它,中文容易在传输过程中变乱码;AUTO_INCREMENT 主键是新增页面回填主键的依赖,缺了它 MyBatis 的 useGeneratedKeys 拿不到自增 ID。如果脚本里是 MyISAM 或者 CHARSET=utf8,建议导入后手动改成 InnoDB 和 utf8mb4,否则后续多张表并发写时容易出现锁问题。
2.2 命令行导入的两种标准姿势
本机已装 MySQL 的前提下,我一般用命令行导入,比图形工具更可控。先建库再导入:
mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -uroot -p supermarket < supermarket.sql已经进了 mysql 客户端时,用 source 效果一样:
mysql -uroot -p mysql> SET NAMES utf8mb4; mysql> SOURCE D:/work/supermarket.sql;参数说明:-u 指定用户名,-p 让客户端交互式提示输入密码,-e 表示执行后面的 SQL 后立刻退出;<是 shell 重定向,把文件内容按字节喂给 mysql 客户端。source 是 mysql 客户端内部命令,路径里的分隔符要用正斜杠,Windows 下写成 D:/ 而不是 D:\,反斜杠会被当成转义字符。导入时如果看到 ERROR 1064 语法错误,通常是脚本里的注释或写法与当前 MySQL 版本不兼容,定位到报错行号附近,看是不是用了 MySQL 8.0 才支持的语法。如果脚本开头没有 CREATE DATABASE,建库时库名必须和后面 jdbc.properties 里写的保持一致,这是新手翻车最高发的位置。
提示:脚本里如果有 CREATE DATABASE 但你想用另一个库名,导入前先做一次全局替换,否则表会落到脚本指定的库里。
2.3 导入后必须做的三个验证
导入成功不表示万事大吉,我用三个查询确认结果:
USE supermarket; SHOW TABLES; -- 看表数量是否和脚本里的建表语句对得上 SELECT COUNT(*) FROM tb_product; -- 抽一张业务表,确认演示数据导入了 DESC tb_product; -- 确认字段、类型、默认值和注释都在SHOW TABLES 检查脚本有没有完整执行,如果中途某条 SQL 报错,后续语句可能全部没跑;SELECT COUNT(*) 验证 INSERT 批量插入有没有丢失数据;DESC 看字段属性,这一步能提前发现「脚本里 stock 默认值没写,页面新增后库存是 null」这类问题。如果发现少表少数据,直接重新执行一次脚本,前提是脚本里有 DROP TABLE IF EXISTS,否则重复建表会报 already exists。超市管理系统的表再多,业务上也逃不出下面这几类:
| 常见表名 | 职责 | 对应的业务模块 |
|---|---|---|
| tb_product | 商品主表 | 商品管理、库存查询 |
| tb_category | 商品分类 | 分类树、下拉框 |
| tb_supplier | 供应商 | 进货管理 |
| tb_stock_record | 出入库流水 | 库存变动记录 |
| tb_sale_order | 销售单主表 | 收银、订单 |
| tb_sale_order_item | 销售单明细 | 销售明细、报表 |
| tb_user | 后台账号 | 登录与权限 |
| tb_member | 会员资料 | 会员管理 |
这张对应关系表的作用是让你拿到 .sql 后,能快速把数据库和业务页面连起来。后面改需求时,改哪张表、动哪段 SQL,都从这张表出发。
2.4 本机没有 MySQL 的两条路:安装版与 Docker 版
没有 MySQL 环境时,常见做法是装 MySQL 8.0 以上版本,安装时字符集选 utf8mb4。这里不展开 mysql 安装配置教程,只提醒两个点:安装时设置的 root 密码要记牢,后面 jdbc.properties 里要写同一个;如果用 MySQL 8.0,驱动类名要写成 com.mysql.cj.jdbc.Driver。另一条路是 Docker,适合不想污染本机环境的场景:
docker run -d --name supermarket-mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -e MYSQL_DATABASE=supermarket \ mysql:8.0 docker exec -i supermarket-mysql mysql -uroot -p123456 supermarket < supermarket.sql参数说明:-e MYSQL_DATABASE 会在容器首次启动时自动建库,省掉手动建库一步;-p 3306:3306 把容器 3306 映射到宿主机,本地 jdbc.url 直接连 localhost:3306 即可;docker exec -i 配合重定向在容器内执行导入。容器默认字符集可能是 latin1,导入前最好先 exec 进去执行 SET NAMES utf8mb4。用 Navicat for MySQL 或 MySQL Workbench 导入也可以,但图形工具的「运行 SQL 文件」有时会忽略 USE 语句,库选错是高频翻车点,命令行方式更不容易出偏差。
3. 对准 ssm 三层配置:Spring、SpringMVC、MyBatis 的职责边界
数据落库后,下一个动作不是急着写代码,而是确认工程里的配置文件能和本地环境对上。SSM 没有 Spring Boot 的自动装配,每个 bean 都要显式声明,一个路径写错,启动时要么报 ClassNotFoundException,要么报 Invalid bound statement。先分清三个框架的职责:Spring 管对象创建和事务,SpringMVC 管请求分发和视图,MyBatis 管 SQL 执行。
3.1 项目目录结构与三层代码的对应关系
这类 ssm 工程的标准目录结构遵循 Maven 约定的 src/main 布局:
src/main/java/com/supermarket/ controller/ 商品、订单、用户的控制器 service/ 业务接口和实现 mapper/ MyBatis 数据访问接口 entity/ 对应数据库表的 JavaBean src/main/resources/ applicationContext.xml Spring 根容器 spring-mvc.xml SpringMVC 子容器 jdbc.properties 数据库连接参数 mapper/ MyBatis 映射文件 src/main/webapp/ WEB-INF/web.xml Servlet 入口配置 WEB-INF/jsp/ 登录、商品列表等页面理解这个结构的意义在于:SSM 是父子容器,applicationContext.xml 管 service、mapper 和事务,spring-mvc.xml 只管 controller 和视图解析。如果两个文件把扫描包写重了,事务代理容易被覆盖,最常见的症状是「查询正常,新增或删除静默不生效」。
配置文件和职责的对应关系可以收成一张表:
| 配置文件 | 容器角色 | 关键声明 | 改环境时动哪里 |
|---|---|---|---|
| web.xml | 入口 | DispatcherServlet、ContextLoaderListener、编码过滤器 | servlet 映射、contextConfigLocation 路径 |
| applicationContext.xml | 父容器 | 数据源、SqlSessionFactory、事务、mapper 扫描 | properties 路径、实体包名 |
| spring-mvc.xml | 子容器 | controller 扫描、视图解析器、注解驱动 | 扫描包名、jsp 前缀后缀 |
| jdbc.properties | 参数 | 驱动、url、账号、密码 | 库名、密码、时区参数 |
| mapper/*.xml | 数据层 | SQL、参数类型、返回类型 | 表名、列名、resultType |
3.2 jdbc.properties 是第一个要改的文件
拿到项目先打开 jdbc.properties,不用管其他配置,改了它才能连上刚才建的库:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/supermarket?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=123456参数说明:com.mysql.cj.jdbc.Driver 是 MySQL 8.0 的驱动类名,老工程里写的 com.mysql.jdbc.Driver 在 8.0 下也能跑,但控制台会打印过时警告;URL 里的 supermarket 要和第二章导入脚本时用的库名完全一致;serverTimezone 必须显式指定,否则 MySQL 8.0 驱动拿不到系统时区会直接抛异常;useSSL=false 关掉 SSL 握手告警,本地开发没必要开。密码含特殊字符时要小心,像 & 或 # 在 properties 里会被截断或当成注释,建议用纯字母数字密码,或者在密码外面不写引号直接替换成转义写法。
3.3 applicationContext.xml 里四个必须对齐的配置
Spring 根容器里,和数据层相关的核心是四段声明:
<context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <property name="typeAliasesPackage" value="com.supermarket.entity"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.supermarket.mapper"/> </bean>逐个说明:property-placeholder 负责把 jdbc.properties 的占位符替换成真实值,location 写错会直接抛 Could not resolve placeholder;dataSource 用 dbcp2 连接池,如果 pom 里引的是 druid,把 class 换成 com.alibaba.druid.pool.DruidDataSource 即可,行为基本等价,只是 Druid 额外带监控页;sqlSessionFactory 是 MyBatis 和 Spring 的桥,mapperLocations 指定映射文件位置,路径写错时启动不报错,但一调用 mapper 方法就抛 Invalid bound statement;typeAliasesPackage 让 XML 里可以直接写 Product 而不是全限定名;MapperScannerConfigurer 把 mapper 接口注册成 bean,basePackage 没写对,controller 里 @Autowired 注入会直接报找不到类型。
3.4 spring-mvc.xml 和 web.xml 的分工
SpringMVC 子容器通常长这样:
<context:component-scan base-package="com.supermarket.controller"/> <mvc:annotation-driven/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:default-servlet-handler/>component-scan 只扫 controller 包,别把 service 包加进来,否则父子容器重复实例化,事务代理容易失效;annotation-driven 开启 @RequestMapping 解析,漏掉它所有请求都会 404;视图解析器的 prefix 和 suffix 决定 Controller 里 return "product/list" 会去找 /WEB-INF/jsp/product/list.jsp,这个路径必须和实际放 JSP 的位置一致,否则报找不到视图。web.xml 里与这两个配置文件对应的注册关系是:DispatcherServlet 的 init-param contextConfigLocation 指向 spring-mvc.xml,ContextLoaderListener 的 context-param 指向 applicationContext.xml。装反的现象很有意思——启动不报错,但所有 controller 请求全部 404,因为 SpringMVC 容器里没有控制器。
web.xml 里还要确认有没有 CharacterEncodingFilter,没有的话 POST 提交的中文大概率乱码:
<filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>3.5 MyBatis 映射文件里别乱改表名
最后一个配置点是 mapper 目录下的 XML。超市系统里查询商品列表的典型写法:
<select id="selectProductList" parameterType="map" resultType="Product"> SELECT id, product_name, sale_price, stock, status FROM tb_product <where> <if test="keyword != null and keyword != ''"> AND product_name LIKE CONCAT('%', #{keyword}, '%') </if> </where> ORDER BY id DESC </select>namespace 必须是 mapper 接口的全限定名,id 必须和接口方法名一致,这两处对不上,启动能过但调用即报错;resultType 写 Product 是因为配了 typeAliasesPackage,没配就写全限定名;<where>加<if>是 MyBatis 动态 SQL 的经典组合,keyword 为空时 WHERE 子句不会生成;LIKE 拼接用 CONCAT 而不是 '%${keyword}%',前者走预编译防注入,后者是字符串替换,别用后者。改字段时记住一个原则:查询列和 resultType 的实体属性一一对应,实体里没有的属性页面取不到值。如果列表页在大数据量下变慢,检查 tb_product 上有没有对 product_name 这类用于查询的列建普通索引,脚本里没有就手动补 ALTER TABLE tb_product ADD INDEX idx_name(product_name)。
4. IDEA + Tomcat 部署:把 ssm 工程跑起来并处理启动故障
配置都对上后进入部署环节。老 ssm 项目的部署方式和 Spring Boot 完全不同,没有内置 Tomcat,也没有 java -jar,要手动加一个 Tomcat Server,并把工程打成一个 Artifact 部署进去。常见做法是 IDEA 里配 Tomcat Local,部署方式选 war exploded。
4.1 导入工程后先做三件前置检查
用 IDEA 打开解压后的项目文件夹,右上角提示 Unlinked Maven Project 时点导入。等待依赖拉取的同时检查三件事:Project Structure 里 Project SDK 是否切到项目要求的 JDK 版本,老项目多半是 1.8;Maven 设置里是否配置了本地仓库,依赖下载失败先来这一层找原因;pom.xml 里 spring、mybatis-spring、druid 等版本之间有没有冲突。前置检查做完再启动,能省掉一半排错时间。
4.2 配置 Tomcat 和 Artifact 的完整路径
菜单 Run → Edit Configurations → 左上角 + → Tomcat Server → Local,然后按四步走:
- Server 标签页里 Application server 指向本机 Tomcat 解压目录
- Deployment 标签页点 + 选 Artifact,类型选 工程名:war exploded
- Application context 改成 /supermarket,和首页跳转路径保持一致
- 确认 HTTP port 没有和本机其他服务冲突
war exploded 是展开目录方式,开发时改 JSP 和静态资源不用重启,刷新页面就能看到效果;war 包方式适合最后打包交付,不适合开发调试。Application context 不一致时,浏览器访问 http://localhost:8080/ 会 404,必须带上下文路径才能进登录页。如果系统里其他服务占用了 8080,在这里改一个端口即可。
注意:启动报错时,找报错信息里的 Caused by,那才是根因,上面一长串框架异常大多是它的表象。
4.3 五种高频启动报错与处理
把老 ssm 项目部署中最容易碰到的报错整理成一张对照表:
| 报错关键字 | 根因 | 处理方向 |
|---|---|---|
| Table doesn't exist | 数据库脚本没导入或库名不对 | 回到第二章重新导入,核对 jdbc.url 库名 |
| Invalid bound statement | mapper XML 没被扫描或 namespace 不对 | 检查 mapperLocations 和 XML 的 namespace |
| Access denied for user | 密码错误或用户无权限 | 改 jdbc.properties,或用 root 重新授权 |
| Port 8080 was already in use | 端口被占用 | 杀进程或改 Tomcat 端口 |
| Unable to compile class for JSP | JDK 版本过高或缺少依赖 | 切 JDK 8,补 tomcat-embed-jasper |
新手最容易混的是前两项:Table doesn't exist 是数据库层面的事,报错信息里能看到具体表名;Invalid bound statement 是 MyBatis 映射层面的事,报错信息里能看到接口方法名。看到关键字再动手,不要盲目重启十次。
4.4 端口占用和日志定位的两个命令组合
Tomcat 端口被占是最常见的启动失败原因,两个命令解决:
# Windows netstat -ano | findstr :8080 taskkill /PID <进程号> /F # Linux / macOS lsof -i:8080 kill -9 <进程号> # 看最新启动日志 tail -f /path/to/tomcat/logs/catalina.outnetstat -ano 列出所有端口占用,findstr :8080 过滤出 8080 的行,最后一列是 PID,用 taskkill /PID 强杀;lsof -i:8080 在 macOS 和 Linux 上都能用,能看到占用进程名。启动报错时不要只看 IDEA 控制台前几行,Tomcat 自己的日志在 logs/catalina.out,Caused by 开头那段才是值得看的根因。
4.5 Maven 依赖拉不下来的换源处理
老项目依赖多,中央仓库网络不稳定时,Maven 面板里一片红色依赖。在 ~/.m2/settings.xml 里加阿里云镜像:
<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <url>https://maven.aliyun.com/repository/central</url> </mirror>mirrorOf 写 central 表示只替换中央仓库,不影响自己配过的私有仓库;换完源回到 IDEA 右侧 Maven 面板点刷新。如果某个 jar 依然红色,去本地仓库 ~/.m2/repository 下找到对应目录整个删掉再刷新,避免留下半截损坏的文件。老项目如果用的是 Druid 连接池,还要确认 druid 版本和 MySQL 8 驱动兼容,版本太旧会在 getConnection 时直接抛异常。
5. 二次开发技巧:按业务表扩字段、扩模块的落地路径
源码跑通只是开始,实际需求大多是改业务。超市管理系统最常见的改动,一是给现成表加字段,二是复制一个最小模块做新功能。SSM 老工程没有代码生成器,改动必须按固定顺序走,漏一层就静默失败。
5.1 最小改动:给商品表加低库存预警字段
给商品加「预警库存」是典型需求。第一步改表:
USE supermarket; ALTER TABLE tb_product ADD COLUMN warn_stock INT DEFAULT 0 COMMENT '低库存预警值' AFTER stock;然后按「实体 → Mapper XML → Service → JSP」顺序同步四层:entity/Product.java 加 warnStock 属性;ProductMapper.xml 里 insert、update、select 语句补 warn_stock 列;Service 和 Controller 通常透传实体不用改;productForm.jsp 加输入框,productList.jsp 显示预警值。这个顺序是唯一不容易漏的路径,改错任何一层,页面要么 500 要么拿到 null。
5.2 列表页直接显示预警状态
显示端直接在 JSP 用 EL 判断,不需要动 Controller:
<c:if test="${product.stock <= product.warnStock}"> <span class="label label-danger">库存预警</span> </c:if>前提是对应 select 语句查出 warn_stock,且实体有 getWarnStock()。漏掉任何一个,标签要么不渲染,要么直接抛异常。
5.3 验证链路:页面 → 日志 → 数据库
改完重启,按一条链路验证:登录 → 商品管理 → 新增商品并填预警值 → 保存 → 列表页看标签 → 用 SQL 查落库:
SELECT id, product_name, stock, warn_stock FROM tb_product ORDER BY id DESC LIMIT 5;列表没显示时先走这条 SQL,再按 F12 看页面返回的 HTML 里有没有 warnStock 值,逐段定位是查询没带出字段还是页面没渲染。
5.4 复制模块的替换要点
要整块新功能,比如供货商管理,直接在源码里找一个最简单的模块(分类管理通常是),复制五份:controller、service、mapper 接口、mapper XML、JSP,然后全局替换类名和表名。最隐性的是 mapper XML 里 namespace 和 id,这两个必须和新接口的方法对应上,否则启动正常、点开即报错。替换完把新表字段逐一比对,漏掉的列在 XML 里补,实体里补属性。最终判断标准只有一个:从登录后的列表页点进新增,填数据保存,数据库能查到这条记录,这条模块链路就算通了。
本文还有配套的精品资源,点击获取