简介:这份资源是面向高校计算机专业学生与Java初学者的一套SSM框架课程设计完整项目,围绕超市管理场景,帮助读者完成从框架整合到业务模块落地的实战练习,适合作为毕业设计或课程作业的参考方案。压缩包共792个文件,约1.78MB,以452个html页面、48个java源文件、48个class编译文件、41个js脚本、28个xml配置及12个jsp视图为主,另含css样式、图片素材与sql建库脚本,覆盖前端页面、后端控制器与数据库脚本的完整链路。已有86人学习下载。项目按商品管理、库存管理、销售管理、会员管理与统计报表等模块组织,后端基于Spring依赖注入与事务管理、Spring MVC处理请求、MyBatis完成SQL映射,前端采用JSP与HTML、CSS、JavaScript构建界面,读者可据此理解分层架构、掌握控制器与实体类设计,并借鉴事务一致性与数据库交互的排错思路。
1. 课程设计选 SSM 做超市管理系统:这套源码到底能帮你省下多少时间
每年到了课程设计季,后台被问得最多的一类问题就是:有没有一套能跑起来、结构清楚、还能改得动的 SSM 项目?超市管理系统算是被选得最多的题目之一,原因很实在——业务模块够多(商品、库存、订单、供应商、会员),但每个模块的逻辑又不算复杂,正好能把 Spring MVC 的请求分发、MyBatis 的映射配置、事务控制这几块串起来练一遍。这套基于 SSM 的超市管理系统源码,走的就是这条路线:后端用 Spring + Spring MVC + MyBatis 三层结构,前端是常规的 JSP/HTML 页面配合少量 JavaScript,数据库用 MySQL,整体是一个标准的 Maven Web 工程。它解决的不是“高并发秒杀”那种炫技需求,而是让你在有限时间里拿到一个分层清晰、能本地跑通、能对着改的骨架。适合谁?正在做课程设计、需要一套可复现 SSM 范例的在校开发者,以及想回头补一补传统三层架构基本功的初级后端。下面我按“先看结构、再跑起来、然后改一处业务、最后排坑”的顺序拆。
2. 拆开工程看分层:Spring MVC 请求链路与 MyBatis 映射是怎么接上的
拿到一套 SSM 源码,最忌讳上来就点运行。先花二十分钟把目录和配置读一遍,后面能少踩一半的坑。这套工程的结构是典型的 Maven 标准布局,src/main/java下按controller / service / service.impl / mapper / entity分包,src/main/resources放 MyBatis 映射文件和数据库配置,src/main/webapp放 JSP 页面和静态资源。理解它的关键,是把“一个浏览器请求进来之后,到底经过了哪些层”这条链路在脑子里跑通。
2.1 从 web.xml 到 DispatcherServlet:请求入口在哪
Spring MVC 的核心是前端控制器DispatcherServlet,它由web.xml里的 Servlet 配置接管。打开web.xml,你会看到两段关键配置:一段是ContextLoaderListener,负责加载 Spring 的根容器(Service、Mapper、数据源这些);另一段是DispatcherServlet,负责加载 Spring MVC 的子容器(Controller、视图解析器这些)。两个容器是父子关系,子容器能拿到父容器的 Bean,反过来不行——这一点决定了你后面把 Service 注入 Controller 没问题,但别指望在 Service 里注入 Controller。
<!-- web.xml 关键片段:两个容器各管一摊 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>这段配置的含义要拆开看:contextConfigLocation指向applicationContext.xml,里面通常配数据源、SqlSessionFactory、事务管理器和组件扫描(扫描service包);DispatcherServlet的init-param指向spring-mvc.xml,里面配注解驱动、视图解析器和 Controller 扫描。url-pattern写成/而不是/*,是为了让静态资源(css、js、图片)不被 DispatcherServlet 全部拦截,这是新手最容易忽略的一个细节。load-on-startup设为 1 表示容器启动时就初始化 Servlet,而不是等第一个请求来了才初始化,调试阶段建议保留,能更早暴露配置错误。
2.2 Controller → Service → Mapper:一条商品查询的完整链路
以“商品列表查询”为例,链路是这样的:浏览器请求/goods/list,DispatcherServlet根据@RequestMapping找到对应的 Controller 方法,Controller 调用注入的 Service,Service 调用 Mapper 接口,Mapper 接口通过 MyBatis 的映射文件执行 SQL,结果逐层返回,最后 Controller 把数据放进Model,视图解析器拼出 JSP 路径渲染页面。
@Controller @RequestMapping("/goods") public class GoodsController { @Autowired private GoodsService goodsService; @RequestMapping("/list") public String list(Model model) { // Service 返回商品集合,塞进 Model 供 JSP 遍历 List<Goods> goodsList = goodsService.findAll(); model.addAttribute("goodsList", goodsList); return "goods/list"; // 视图解析器会拼成 /WEB-INF/jsp/goods/list.jsp } }这里有两个参数点值得注意。第一,@Autowired注入的是接口GoodsService,实际实现类在service.impl包里,靠 Spring 的组件扫描和接口多态完成装配,所以实现类上必须有@Service注解,否则启动就报找不到 Bean。第二,返回值"goods/list"是逻辑视图名,真正的物理路径由spring-mvc.xml里的InternalResourceViewResolver决定,常见配置是前缀/WEB-INF/jsp/、后缀.jsp。如果你改了 JSP 的存放位置,记得同步改这里,不然就是 404。
2.3 MyBatis 映射文件:namespace 与 resultMap 的对应关系
MyBatis 这一层,新手翻车最多的是namespace和接口对不上。映射文件里的namespace必须写 Mapper 接口的全限定名,select的id必须和方法名一致,否则启动时报Invalid bound statement。这套工程的映射文件放在resources/mapper下,applicationContext.xml里通过mapperLocations通配扫描。
<!-- GoodsMapper.xml 片段 --> <mapper namespace="com.demo.mapper.GoodsMapper"> <resultMap id="goodsMap" type="com.demo.entity.Goods"> <id column="goods_id" property="goodsId"/> <result column="goods_name" property="goodsName"/> <result column="stock" property="stock"/> </resultMap> <select id="findAll" resultMap="goodsMap"> SELECT goods_id, goods_name, stock FROM goods ORDER BY goods_id DESC </select> </mapper>resultMap的作用是解决数据库列名(下划线风格)和 Java 属性名(驼峰风格)不一致的问题。如果你图省事直接写resultType,那要么在applicationContext.xml里开启mapUnderscoreToCamelCase,要么就得保证列名和属性名完全一致,否则查出来的字段全是 null——这个现象很迷惑,SQL 明明有数据,页面上就是不显示,血泪经验是先在控制台打印一下返回对象。另外ORDER BY这类排序条件如果来自前端参数,别直接字符串拼接,用#{}占位或者做白名单校验,这是后面避坑章节要展开的点。
3. 本地跑通这套系统:环境、建库与启动的完整步骤
结构看明白了,接下来是把它跑起来。这一步的目标很明确:浏览器能打开登录页,能用默认账号进到商品列表。整个过程分环境准备、建库导数据、改配置、启动验证四步,任何一步跳过都会在启动时报错,所以按顺序来。
3.1 环境版本对齐:JDK、Tomcat、MySQL 怎么选
这套工程是传统 SSM,不是 Spring Boot,所以没有内嵌容器,需要外置 Tomcat。版本上我一般这样配:JDK 8(工程里的pom.xml如果写了source和target为 1.8,就别用 JDK 17 硬上,编译能过但运行期反射和 JSP 编译容易出问题)、Tomcat 8.5 或 9.0(Tomcat 10 把javax.*换成了jakarta.*,老工程直接部署会大面积报 ClassNotFound)、MySQL 5.7 或 8.0(8.0 要注意驱动类名和时区参数)。Maven 用 3.6 以上即可。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 | 与 pom 编译级别保持一致 |
| Tomcat | 8.5 / 9.0 | 避免 jakarta 命名空间问题 |
| MySQL | 5.7 / 8.0 | 8.0 需改驱动与 URL 参数 |
| Maven | 3.6+ | 用于依赖拉取和打包 |
版本对齐这件事看着琐碎,但它决定了你后面是“调业务”还是“调环境”。我见过太多人卡在启动阶段,最后发现是 Tomcat 10 部署了javax工程,白折腾一下午。
3.2 建库与导入 SQL:字符集和自增主键的两个细节
工程一般会附带一个sql目录或者根目录下的.sql文件。导入之前先建库,字符集用utf8mb4,排序规则utf8mb4_general_ci,这样中文商品名不会乱码。
CREATE DATABASE supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE supermarket; -- 再执行工程自带的建表与初始化数据脚本 SOURCE /path/to/supermarket.sql;导入后做两件事验证:一是SHOW TABLES;看表数量是否和预期一致(商品、分类、库存、订单、用户这几张核心表要在);二是随便SELECT * FROM goods LIMIT 5;看有没有初始数据。如果表建好了但数据是空的,登录页大概率进不去,因为账号密码存在用户表里。另一个细节是自增主键的起始值,如果脚本里写了AUTO_INCREMENT=1000之类,新增商品时 ID 会从 1000 开始,这不影响功能,但如果你在代码里硬编码了 ID 判断,就会出问题——所以别硬编码。
3.3 改数据源与连接参数:MySQL 8 的驱动和时区坑
数据源配置在applicationContext.xml或独立的jdbc.properties里。MySQL 5.7 和 8.0 的配置差异集中在驱动类名和 URL 参数上,这是必须改对的地方。
# MySQL 5.7 写法 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/supermarket?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=你的密码 # MySQL 8.0 写法(驱动类名变了,URL 要加时区和 SSL 参数) jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/supermarket?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true参数逐个说清楚:serverTimezone不配,MySQL 8 驱动会报时区无法识别,表现为启动时连接池初始化失败;useSSL=false是本地开发关掉 SSL 握手,避免证书告警;allowPublicKeyRetrieval=true是 8.0 默认认证插件caching_sha2_password在非 SSL 连接下需要的开关,不加上会报Public Key Retrieval is not allowed。密码别用明文提交到版本库,本地开发图省事可以,但养成放jdbc.properties并加进.gitignore的习惯。
3.4 部署到 Tomcat 并验证:从登录页到商品列表
配置改完,用 Maven 打包成 war,丢进 Tomcat 的webapps目录,启动bin/startup.bat(Windows)或startup.sh(Linux)。访问http://localhost:8080/工程名/,应该能看到登录页。用 SQL 脚本里的初始账号登录,进到后台首页,点商品管理,列表能出来,说明 Controller、Service、Mapper、数据库这条链路全通了。
# 打包并部署的典型命令 mvn clean package -DskipTests cp target/*.war $TOMCAT_HOME/webapps/ $TOMCAT_HOME/bin/startup.sh # 看日志确认无异常 tail -f $TOMCAT_HOME/logs/catalina.out验证顺序建议是:先看 Tomcat 启动日志有没有SEVERE,再看应用日志有没有 Spring 容器初始化异常,最后才是页面。如果页面 404,先确认访问路径里的工程名和 war 包名一致;如果页面 500,看catalina.out里的堆栈,八成是某个 Bean 没注入或者 SQL 映射找不到。这一步跑通,后面改业务才有意义。
4. 改一处真实业务:给商品模块加一个按名称模糊查询
跑通之后,最有价值的练习不是继续点页面,而是动手改一个功能,验证自己真的理解了分层。选“商品按名称模糊查询”最合适:它同时涉及前端传参、Controller 接收、Service 组装条件、Mapper 写动态 SQL 四个环节,改完这一处,整套架构你就摸透了。
4.1 前端传参与 Controller 接收:@RequestParam 的取舍
在商品列表页加一个搜索框,表单提交到/goods/search,参数名keyword。Controller 里用@RequestParam接收,注意设required = false,否则不传参数会直接 400。
@RequestMapping("/search") public String search(@RequestParam(value = "keyword", required = false) String keyword, Model model) { // 关键字为空时退化为全量查询,避免拼出 LIKE '%%' 之外的空条件 List<Goods> goodsList = goodsService.searchByName(keyword); model.addAttribute("goodsList", goodsList); model.addAttribute("keyword", keyword); // 回填搜索框 return "goods/list"; }required = false是关键参数,它允许搜索框为空时也能进方法,方法内部再做空值判断。model.addAttribute("keyword", keyword)是为了页面回显,用户体验上的小细节,但课程设计答辩时这种细节很加分。别用@RequestBody接表单参数,那是给 JSON 请求体用的,普通表单提交用@RequestParam或直接方法参数绑定。
4.2 Service 与 Mapper 动态 SQL:用<if>和CONCAT防注入
Service 层做空值兜底,Mapper 层用 MyBatis 的动态 SQL 标签拼条件。这里必须用#{}而不是${},前者是预编译占位符,能防 SQL 注入,后者是字符串直接拼接,是安全漏洞。
<select id="searchByName" resultMap="goodsMap"> SELECT goods_id, goods_name, stock FROM goods <where> <if test="keyword != null and keyword != ''"> goods_name LIKE CONCAT('%', #{keyword}, '%') </if> </where> ORDER BY goods_id DESC </select><where>标签会自动处理AND前缀问题,条件为空时不会拼出多余的WHERE。CONCAT('%', #{keyword}, '%')是标准写法,#{}会被编译成?占位符,参数由 JDBC 安全绑定。如果你写成LIKE '%${keyword}%',用户输入' OR '1'='1就能把整张表拖出来,这是课程设计里最容易被答辩老师追问的安全点。改完这一处,重新打包部署,输入“可乐”能筛出对应商品,功能就闭环了。
5. 这套源码最容易翻车的地方:五条排查记录
跑通和改通之后,剩下的时间基本都花在排错上。下面五条是我拆这类 SSM 工程时反复遇到的,按“现象 → 原因 → 解决”记下来,你遇到时能少走弯路。
5.1 启动报 Invalid bound statement:namespace 或 id 对不上
现象是应用启动或首次查询时抛org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)。原因通常是映射文件的namespace没写 Mapper 接口全限定名,或者select的id和方法名不一致,也可能是mapperLocations没扫描到该文件。解决:先核对namespace与接口包路径逐字符一致,再核对方法名,最后确认applicationContext.xml里mapperLocations的通配路径(常见classpath:mapper/*.xml)能覆盖到文件所在目录。改完 clean 一次再打包,避免旧 class 残留。
5.2 页面字段全是 null:列名与属性名没映射上
现象是 SQL 在数据库客户端能查出数据,但页面上商品名、库存全是空白。原因是resultType直接映射时,数据库下划线列名和 Java 驼峰属性名对不上。解决:要么改用resultMap显式写column与property的对应,要么在 MyBatis 配置里开启mapUnderscoreToCamelCase=true。排查技巧是在 Service 返回后打一行日志把对象打印出来,确认是查询层问题还是 JSP 取值问题,别一上来就怀疑前端。
5.3 中文商品名乱码:三处字符集要一起改
现象是新增商品后列表显示问号或乱码。原因可能出在数据库、连接 URL、JSP 页面三处任意一处。解决:数据库和表用utf8mb4;JDBC URL 带useUnicode=true&characterEncoding=utf8;JSP 页面顶部声明<%@ page contentType="text/html;charset=UTF-8" %>。三处缺一不可,只改一处往往还是乱码,这是典型的“改对了但没全对”。
5.4 事务不生效:注解加错位置或没开事务管理
现象是下单扣库存时,扣减成功但订单插入失败,库存却没回滚。原因是@Transactional加在了 Controller 上,或者applicationContext.xml没配DataSourceTransactionManager和<tx:annotation-driven/>。解决:事务注解加在 Service 实现类的public方法上,确认事务管理器 Bean 存在且注解驱动已开启。另外注意同类内部方法自调用不会触发代理,事务同样失效,这是 Spring AOP 的经典边界。
5.5 静态资源 404:DispatcherServlet 拦截了 css 和 js
现象是页面能打开但样式全丢,控制台一堆 404。原因是web.xml里url-pattern写成了/*,把所有请求都交给了 DispatcherServlet。解决:改成/,并在spring-mvc.xml里配<mvc:default-servlet-handler/>或<mvc:resources>放行静态资源目录。这个坑在课程设计里极常见,因为很多人是从网上抄的web.xml片段,没注意这一个字符的差别。
6. 从能跑到能讲:用日志和断点把 SSM 链路验证成自己的东西
课程设计的终点不是“页面能点”,而是答辩时被问到“这个请求怎么走到数据库的”你能张口就答。我的习惯是挑一条链路,用日志和断点把它完整走一遍,把每一层的输入输出都看清楚。具体做法:在 Controller 方法入口打一行log.info打印请求参数,在 Service 实现类打一行打印业务处理前后的对象,在 Mapper 对应的 SQL 上开 MyBatis 的 SQL 日志(applicationContext.xml里配logImpl为STDOUT_LOGGING或接 logback),这样一次请求下来,参数、SQL、返回结果全在控制台排成一条线。
<!-- 开启 MyBatis 控制台 SQL 日志,调试期用,上线关掉 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <property name="configuration"> <bean class="org.apache.ibatis.session.Configuration"> <property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"/> </bean> </property> </bean>配上之后,控制台会打印Preparing: SELECT ...和Parameters: 可乐(String),你能直观看到#{}被替换成?再绑定参数的过程,这比看十篇原理文章都管用。断点则建议打在 Service 实现类的第一行,用 IDE 的 Debug 模式启动 Tomcat,单步跟到 Mapper 调用,观察代理对象是怎么把接口方法翻译成 SQL 的。这套验证方法的价值在于:它把“黑匣子”变成了“透明管道”,以后换任何一套 SSM 工程,你都能用同样的方式快速定位问题在哪一层。
再补一个进阶技巧:把logImpl换成 logback 并配置独立的 MyBatis 日志级别,可以按包名过滤,只打自己项目的 SQL,避免被框架日志淹没。另外,调试完记得把STDOUT_LOGGING关掉,生产环境打全量 SQL 既影响性能又可能泄露敏感字段。从那以后我每次接手一套陌生的 SSM 工程,都强制先开一次 SQL 日志、走一遍登录到列表的链路,确认分层和映射都对得上,再动业务代码——这个习惯帮我省掉了大量“改了不知道哪层出问题”的时间。希望帮到你。
本文还有配套的精品资源,点击获取