SSM框架后台管理系统实战:环境搭建、MyBatis动态SQL与Shiro权限控制
简介这是一份基于SSM框架的养老院后台管理系统毕业设计源码与数据库资源主要面向计算机相关专业正在准备毕业设计的学生以及需要项目实战练习的Java学习者同样适用于课程设计和期末大作业。压缩包共70个文件以45个Java源码、20个XML配置、1个数据库SQL脚本及少量属性文件和说明文档为主整体约43KB文件结构紧凑便于快速定位与导入开发环境。目前已有696人学习下载资源适合直接作为毕设项目使用。项目后台采用SSM框架并配合MySQL数据库开发环境为JDK与IDEA已经过导师指导与严格调试可稳定运行内含完整项目源码、数据库脚本、开发工具指引和项目说明能帮助读者理解养老院后台管理系统的分层架构与业务实现思路也可为毕业设计答辩或课程设计提供参考。1. 拿到“养老院后台管理系统”压缩包后先别急着 import 工程把“SSM框架养老院后台管理系统源码数据库.zip”解压后目录里通常躺着三样东西一个 Maven 工程或普通 Web 工程、一个 .sql 数据库脚本、一份说明文档。这个结构决定了它的定位——不是生产级 SaaS而是把 Java 后端三大框架 Spring、Spring MVC、MyBatis 串起来的完整 demo附带建表语句和种子数据适合做毕业设计二次开发也适合刚入职的初级工程师拿来看懂一套后台管理系统从数据库到控制器的数据流。但这类压缩包最容易在第一步就劝退人IDEA 里 import 之后疯狂报红Tomcat 启动直接 404或者数据库连不上。问题大多不是源码本身而是 JDK 版本、Maven 仓库、MySQL 字符集、Tomcat 路径这几样环境变量没对齐。本文会从工程结构和依赖入手先讲清楚 SSM 三个框架在这个项目里各自扮演什么角色再给出一套能复现的最小启动流程最后落到 Shiro 权限控制和 MyBatis 动态 SQL 这两个实际开发中最常被打磨的环节——这两个点也是面试时围绕 SSM 项目最容易被追问的地方。全文不会出现虚构的官方文档只会讲一线工程里验证过的做法和参数边界。2. 先分清 SSM 里三个框架各自管什么再谈改代码2.1 Spring 是容器Spring MVC 是 HTTP 入口MyBatis 是 SQL 映射器这套组合里Spring 负责管理对象生命周期和依赖注入被管理对象叫 Bean。养老院管理系统里的 Service、Mapper 接口、DataSource、事务管理器全部注册进 Spring 容器。Spring MVC 是表现层框架DispatcherServlet 接收 HTTP 请求后通过 HandlerMapping 找到对应的 Controller 方法再通过 ViewResolver 把 ModelAndView 解析成 JSP 或 JSON。MyBatis 是持久层框架Mapper 接口的方法名与 XML 文件里的 statement id 对应SQL 写死在 XML 里由 SqlSessionFactory 创建 SqlSession 执行。这个项目的关键在于三个框架的配置文件是分开的但彼此又通过 Spring 容器串联。典型配置是 applicationContext.xml 声明组件扫描、数据源、事务管理器spring-mvc.xml 声明 Controller 扫描、注解驱动、视图解析器。MyBatis 的 SqlSessionFactoryBean 会通过 mapperLocations 属性指向 classpath 下的 mapper 目录然后由 Spring 把 Mapper 接口代理实现注册进容器。2.2 从依赖坐标判断这个工程用的是 SSM 的哪个版本组合不同年代的 SSM 工程依赖版本差异很大直接影响能否跑起来。养老院后台管理系统这类毕业设计多数基于 Spring 4.x 或 5.xMyBatis 3.4.x-3.5.xSpring MVC 与 Spring 同版本号。打开 pom.xml 后重点看这几个坐标框架常见坐标版本范围Spring Spring MVCorg.springframework:spring-context / spring-webmvc4.3.x / 5.2.xMyBatisorg.mybatis:mybatis3.4.x / 3.5.xMyBatis-Spring 桥接org.mybatis:mybatis-spring1.3.x / 2.0.x数据库连接池com.alibaba:druid 或 commons-dbcp1.1.x / 1.4.xServlet APIjavax.servlet:javax.servlet-api3.1.0 / 4.0.1查看方式是在 IDEA 右侧 Maven 工具窗口展开 Dependencies。如果发现 spring-webmvc 和 spring-context 版本不一致或者 mybatis-spring 与 mybatis 版本跨代轻则启动报 NoSuchMethodError重则 Bean 注入失败。常见做法是统一 Spring 家族版本号为同一个MyBatis 与 mybatis-spring 遵循官方兼容矩阵mybatis-spring 2.x 对应 mybatis 3.51.x 对应 mybatis 3.4 及以下。另外JDK 版本决定 Spring 版本上限。Spring 5.2.x 在 JDK 8 上运行没问题但如果你本机装了 JDK 17直接运行 Spring 4.x 工程大概率会报 IllegalAccessError 或 --add-opens 相关错误。建议安装 JDK 8并在 pom.xml 里确认 maven.compiler.source 和 target 都是 1.8。以下是典型 pom.xml 关键片段properties spring.version5.2.22.RELEASE/spring.version mybatis.version3.5.10/mybatis.version /properties dependencies dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency /dependencies这段配置里mybatis-spring 2.0.7 与 mybatis 3.5.10 是兼容组合Spring 5.2.22 与 Servlet 3.1 容器兼容Tomcat 8.5 或 9.0 都可以承载。注意不要混用 javax.servlet 与 jakarta.servlet 依赖Spring 5.x 走的是 javax 命名空间Spring 6.x 才是 jakarta。2.3 web.xml 加载顺序不对Controller 永远进不来SSM 工程没有 Spring Boot 的自动装配所有配置的入口是 web.xml。它的加载顺序决定了 Spring 容器和 Spring MVC 容器谁先创建。先看一个标准配置context-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mappingContextLoaderListener 先读取 applicationContext.xml创建父容器管理 Service、Mapper、DataSource 这些业务层 Bean。DispatcherServlet 启动时再读取 spring-mvc.xml创建子容器管理 Controller。子容器可以拿到父容器的 Bean所以 Service 能被注入到 Controller但父容器不能反向引用子容器的 Bean。/ 会把所有请求都交给 DispatcherServlet包括静态资源。资源请求会被 MappingJackson2JsonView 或 HandlerAdapter 当成一个不存在的 Handler从而 404。解决方式是在 spring-mvc.xml 里加 mvc:default-servlet-handler/ 或者在 web.xml 里单独映射 .js/.css/.jpg 到 DefaultServlet。毕业设计项目很多直接把静态资源放在 webapp/static 下如果访问不到优先检查这一项。3. 数据库脚本导入和 MyBatis 参数设置是启动成败的分水岭3.1 用 Navicat 或命令行导入 .sql 的正确姿势压缩包里附带的数据文件一般是 old_nurse_home.sql 或类似命名里面包含 CREATE DATABASE、CREATE TABLE、INSERT INTO 语句。导入最常见的问题不是 SQL 语法错误而是字符集。养老院系统里的中文数据包括老人姓名、护理等级、家属联系方式等如果库表字符集不是 utf8mb4导入后中文全部变成问号或乱码。命令行导入前先确认两件事CREATE DATABASE IF NOT EXISTS old_nurse_home DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;如果建库语句里只写了 utf8建议改成 utf8mb4。utf8mb4 是 utf8 的超集能存储 Emoji 和生僻字而且与 utf8 的排序规则不冲突。对养老院管理系统来说老人姓名里可能出现生僻字utf8 一个字最多 3 字节生僻字多为 4 字节一旦存在就会报 Incorrect string value 错误。导入命令是mysql -uroot -p --default-character-setutf8mb4 old_nurse_home old_nurse_home.sql加上 --default-character-setutf8mb4 是为了让 mysql 客户端在解析 sql 文件中的字符串时按 utf8mb4 解码避免文件里的中文在被服务端接收前就损坏。如果使用 Navicat导入前在连接属性里把编码设置为 65001UTF-8不要选 GBK。图示路径是连接名右键 - 编辑连接 - 高级 - 编码。3.2 jdbc.properties 里的 5 个参数改错一个连不上SSM 工程的数据库连接信息通常放在 resources/jdbc.properties由 Spring 的 PropertyPlaceholderConfigurer 读取。核心参数如下jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/old_nurse_home?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456 jdbc.maxActive50driver 这一行要注意MySQL 5.7 及以下用 com.mysql.jdbc.DriverMySQL 8.0 必须改成 com.mysql.cj.jdbc.Driver否则驱动类加载时报 ClassNotFoundException。URL 里的 serverTimezone 参数在 MySQL 8.0 下是必填的默认时区与 JVM 时区不一致会导致 java.sql.SQLException: The server time zone value йʱ 这类乱码错误。Asia/Shanghai 是稳妥选项。maxActive 是连接池最大连接数常见做法是设置在 20-50 之间。养老院管理系统的并发量不会太高50 已经够用。如果设成 100MySQL 默认的 max_connections 是 151连接池还没打满数据库就先拒绝了报 Too many connections。压测时调整 maxActive 前先查 MySQL 的 show variables like max_connections。3.3 MyBatis XML 里 resultMap 和动态 SQL 决定业务能不能跑导入数据库后下一步是验证 MyBatis 的 mapper 文件能正确映射。养老院系统的业务围绕老人档案、家属信息、护工排班、床位管理、费用记录展开每个模块一个实体类对应一张表一个 Mapper 接口对应一个 XML 文件。以下是一个典型查询示例resultMap idElderMap typecom.example.entity.OldMan id propertyid columnid/ result propertyname columnname/ result propertycareLevel columncare_level/ result propertybedNo columnbed_no/ association propertynurse javaTypecom.example.entity.Nurse id propertyid columnnurse_id/ result propertyrealName columnnurse_name/ /association /resultMap select idselectElderList resultMapElderMap parameterTypemap SELECT e.id, e.name, e.care_level, e.bed_no, n.id AS nurse_id, n.real_name AS nurse_name FROM old_man e LEFT JOIN nurse n ON e.nurse_id n.id where if testname ! null and name ! AND e.name LIKE CONCAT(%, #{name}, %) /if if testcareLevel ! null and careLevel ! AND e.care_level #{careLevel} /if /where ORDER BY e.id DESC LIMIT #{offset}, #{limit} /select这段 SQL 里 标签会自动去除第一个满足条件下的多余 AND这是 MyBatis 动态 SQL 的常见用法避免手写 11 这种拼凑。LIKE 查询用 CONCAT 拼 %而不是直接在 #{} 里写 %因为 #{} 会预编译成占位符字符串里的 % 不会被识别为通配符。LIMIT #{offset}, #{limit} 是分页查询的老写法offset (pageNum - 1) * pageSizepageSize 对应每页条数。这里推荐在 Service 层计算 offset不要在前端传原始页码。需要特别注意的是 useGeneratedKeys 与自增主键回填。向 old_man 表插入数据后主键 id 由数据库自增MyBatis 默认不会把生成的主键写回实体对象。在很多毕业设计里插入老人档案后马上要用这个 id 去关联家属表拿不到 id 就只能再查一次。正确配置是insert idinsertElder parameterTypecom.example.entity.OldMan useGeneratedKeystrue keyPropertyid INSERT INTO old_man (name, care_level, bed_no, nurse_id) VALUES (#{name}, #{careLevel}, #{bedNo}, #{nurseId}) /insertkeyProperty 对应实体类的 id 字段插入完成后调用 getter 就能拿到新主键。如果没有配置 useGeneratedKeys插入后 id 是 null后续关联操作必挂。3.4 Mapper 接口扫描漏配启动不会报错但请求时 500在 applicationContext.xml 或 spring-mvc.xml 里配置 Mapper 扫描有两种方式MapperScannerConfigurer 或 mybatis:scan 。常见配置如下bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.example.dao/ property namesqlSessionFactoryBeanName valuesqlSessionFactory/ /beanbasePackage 必须与 Mapper 接口所在的包完全一致。如果工程里 dao 包下既有 Mapper 接口又有普通类会全部被当作 Mapper 注册启动时报 Invalid bound statement 或 BeanCreationException。漏配或包名写错时Spring 容器里没有 Mapper 代理对象Service 注入时报 NoSuchBeanDefinitionException这个错在启动阶段就会暴露不会拖到请求期。另一个排错点是 Mapper XML 的 namespace 必须与接口全限定名一致statement id 必须与方法名一致。以下错误信息是微信里被问得最多的org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.example.dao.OldManMapper.selectElderList这类错误的原因是 mapper 接口编译后的 class 文件在 target/classes 里而 XML 文件也在 resources/mapper 下但 pom.xml 没有把 XML 打进最终产物。Maven 默认只打包 resources 目录下的文件如果 XML 放在 src/main/java/com/example/dao 下需要额外配置build resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource /resources /build这一点在导入毕业设计工程时很容易遇到代码在 IDEA 里能看到 XML但 Tomcat 部署的 war 包里没有请求时 Invalid bound statement。建议在项目根目录执行 mvn clean package 后解压 target 下的 war 或 jar确认 mapper 目录存在再排查别的。4. 权限控制养老院后台里的管理员与护工角色怎么落地4.1 用 Shiro 做认证授权还是一律写在 Filter 里SSM 工程里的权限控制有两种常见路径集成 Apache Shiro或自己写 HandlerInterceptor。养老院管理系统的角色包括超级管理员、前台、护工、家属不同角色能看到的功能菜单和操作按钮不同。毕业设计里很多直接在每个 Controller 方法里判断 session 里的 userType这样做虽然快但权限判断散落在各业务代码里改角色权限时要么改 Java 代码重新编译要么漏掉某个入口。案发率最低的做法是集成 Shiro。原因有三点其一Shiro 把认证、授权、会话管理封装成 Filter 链路可以在 web.xml 里配置 URL 拦截规则不需要侵入业务代码其二它天然支持基于角色的访问控制一个注解 RequiresPermissions(oldman:add) 就能在方法粒度上做校验其三登录成功后通过 SecurityUtils 获取当前用户比手动从 session 取参数更规范。缺点是 Shiro 的过滤器链顺序严格写错顺序会直接导致所有请求被匿名拦截。4.2 Shiro 过滤链配置参数对照表在 spring-shiro.xml 里配置 ShiroFilterFactoryBean核心是 filterChainDefinitions。以下是一份养老院后台的常见配置bean idshiroFilter classorg.apache.shiro.spring.web.ShiroFilterFactoryBean property namesecurityManager refsecurityManager/ property nameloginUrl value/login.html/ property nameunauthorizedUrl value/403.html/ property namefilterChainDefinitions value /login.html anon /login anon /css/** anon /js/** anon /images/** anon /logout logout /oldman/** authc /nurse/** authc /bed/** authc, roles[admin] /finance/** authc, roles[admin, cashier] /** authc /value /property /beananon 表示匿名可访问authc 表示必须登录roles[admin] 表示必须拥有 admin 角色logout 表示登出。每一行定义了一个 URL 模式的过滤器链Shiro 按声明顺序匹配所以 /login.html 必须在 /** 前面。roles[admin] 会要求当前 Subject 拥有指定角色这里的角色是字符串与数据库 user 表的 role 字段一一对应。配置 loginUrl 后未登录访问 /oldman/** 会被重定向到登录页但如果是前后端分离的 AJAX 请求重定向会返回 HTML 而不是 JSON此时需要自定义 UserFilter 重写 onAccessDenied 返回 JSON 状态码。实际开发里还有一个坑resources 目录下的静态资源会被 /login.html anon 之外的规则拦截吗不会因为/css/** anon声明在先且 URL 模式匹配路径前缀普通请求先命中的规则就生效。4.3 数据库表结构与 Shiro 的关联模型权限系统的表结构通常至少三张用户表、角色表、用户角色关联表。养老院系统的 user 表可以这样设计CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, salt VARCHAR(32) DEFAULT NULL, role VARCHAR(20) NOT NULL DEFAULT nurse, real_name VARCHAR(32) NOT NULL, status TINYINT DEFAULT 1 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;Shiro 的 Realm 负责从数据库读取用户信息和角色信息。密码存储时不能是明文常见做法是 MD5 加盐或者用 BCrypt。因为 JDK 自带的 MessageDigest 对 MD5 的计算方式是多线程不安全的所以写 Realm 时要用 Shiro 提供的 SimpleHash 来算SimpleHash hash new SimpleHash(MD5, rawPassword, salt, 1024); String encodedPassword hash.toHex();这段代码的四个参数分别为算法名、原始密码、盐值、哈希迭代次数。迭代次数 1024 是 Shiro 推荐的最低值太低的 hash 容易被暴力破解。盐值可以用用户唯一标识比如用户名或 UUID存入数据库 salt 字段。登录校验时Shiro 会调用 Realm 里的 doGetAuthenticationInfo 方法把数据库里的盐值和密文返回给 CredentialsMatcher 做比对不需要业务代码手动比对密码。4.4 方法级权限注解的启用条件Shiro 支持在 Controller 方法上加 RequiresPermissions但不会默认开启。必须在 Spring MVC 配置文件中启用bean classorg.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor property namesecurityManager refsecurityManager/ /bean并且需要开启 CGLIB 代理或 JDK 动态代理。如果 Controller 类没有接口则必须使用 CGLIB默认配置在 Spring 5.x 下已经支持。没有这个 AdvisorRequiresPermissions 注解会被静默忽略权限校验完全失效这是毕业设计里最容易出现的“看起来配了权限但一点用没有”的问题。启用后添加床位删除权限只需RequiresPermissions(bed:delete) GetMapping(/bed/delete) public String deleteBed(Integer id) { bedService.deleteById(id); return redirect:/bed/list; }权限字符串 bed:delete 是自定义的不需要在数据库里维护权限表只要拦截代码与调用方一致即可。建议在常量类里统一定义这些字符串避免手写拼错导致权限永远不通过。5. 毕业设计的完整落地从压缩包跑到可二次开发5.1 按流程走一遍建库、导入、启动、验证拿到压缩包后按照时间线操作每一步都有验证标准不是跑完就完。先确认本机环境推荐组合是 JDK 8、Maven 3.6.x、Tomcat 8.5、MySQL 5.7。JDK 版本与 Spring 版本要匹配Spring 5.2.x 兼容 JDK 8 到 11但 Tomcat 8.5 最高支持 JDK 11如果用 JDK 17 就会遇到模块访问限制问题。按以下步骤操作用 IDEA 的 Open 选择解压后的目录等待 Maven 下载依赖。如果 pom.xml 没有被识别右键 pom.xml - Add as Maven Project。使用 root 账号创建数据库并导入 SQL 脚本。修改 jdbc.properties 里的数据库连接参数。配置 Tomcat。IDEA 里 Run - Edit Configurations - Tomcat Server - LocalDeployment 选项卡中添加 Artifact: exploded。启动 Tomcat观察控制台输出。看到 “Initializing Spring root WebApplicationContext” 和 “Initializing Spring FrameworkServlet dispatcher” 两条日志后说明 Spring 容器和 Spring MVC 容器都起来了。启动后验证方式浏览器访问http://localhost:8080/项目名/login.html。如果首页能正常渲染说明静态资源配置正确。然后输入管理员账号登录跳转到 index.html后台菜单出现养护人员、床位管理、费用管理各模块说明 Shiro 的认证流程和 JSP 模板渲染都通了。接着点击任意一个有新增功能的菜单比如新增老人档案表单提交后到数据库执行 SELECT可查出一条新记录且 id 自增说明 MyBatis 的事务管理和主键回填配置正确。5.2 把通用项目改造成内聚的后台服务时先动这五个文件很多学生在答辩前要加功能或改模块与其新写一个模块不如在现有业务上再做一层。排名靠前的改动点是实体类、Mapper、Service、Controller、前端 JSP 页面。举一个最常见的场景新增“护工排班”功能。数据库需要新建一张 work_schedule 表关联护工表和日期。实体类新建 WorkSchedule 类对应表字段。Mapper 接口新建接口和 XML。Service 层写排班查询和保存逻辑。Controller 暴露页面入口。最后在左侧菜单的 JSP 或 HTML 中加上对应超链接即可。如果源码里使用的是 JSP那么修改后不需要重新编译 Java 代码直接刷新页面就能看到效果。Tomcat 默认开启了 JSP 热加载。但如果改的是 Java 代码需要重新编译并重新部署IDEA 里快捷键 CtrlShiftF10 只会编译当前文件不会自动重新部署到 Tomcat。推荐用 JRebel 或直接点 Tomcat 配置里的 “Update resources”。5.3 前后端分离改造的一个折中方案Controller 返回 JSON部分毕业设计要求做前后端分离标题里也关联了 Vue3 后台管理系统。SSM 工程改造前后端分离不需要完美的 REST 化常见做法是保持 Spring MVC 架构不变把 Controller 里的返回值从 ModelAndView 改成 ResponseBody把 JSP 页面替换成静态 HTML页面上的数据全部走 Axios 请求。RestController RequestMapping(/api/oldman) public class OldManApiController { Autowired private OldManService oldManService; GetMapping(/list) public ResultListOldMan list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer limit) { int offset (page - 1) * limit; ListOldMan list oldManService.selectList(offset, limit); return Result.success(list); } }这段代码里 RestController 替代 Controller ResponseBody 的组合每个方法返回的对象会被 Jackson 序列化为 JSON。Result 是统一响应体包装结构通常包含 code、msg、data 三个字段。前端 Vue3 项目里通过 Axios 拦截器统一处理 code这样后端可以继续复用原来的 MyBatis 代码不需要重写整套接口。对于这个标题里的毕业设计项目来说这是一个收益高、风险低的改造路径不会破坏原有 SSM 结构。6. 高频报错的定位方法从控制台日志反推根因此章提炼几个最常见的错误日志和参数设置错误。解决这些问题不需要背 Spring 源码只要会读日志、会看配置文件、会查表结构就足够。第一个高频错误是Invalid bound statement (not found)前文已提到 XML 未打包的问题。定位方法展开 target/classes 目录确认是否存在 mapper 目录没有则按 Maven resources 配置补全存在则检查 namespace 与接口全限定名是否一致。第二个高频错误是Table old_nurse_home.xxx doesnt exist常见原因是实体类对应的表名与数据库实际表名不一致。比如 Java 类叫 CareLog但数据库表明叫 care_logMyBatis 默认按实体类名驼峰转下划线查找表若不一致需要在 Table 注解或 XML 中明确表名。更稳妥的做法是养成编写 mapper XML 时显式写表名的习惯不依赖自动转换。第三个高频错误是Access denied for user rootlocalhost检查 jdbc.properties 的账号密码同时确认 MySQL 用户表里的 host 是否允许 localhost。如果是 Docker 容器里的 MySQLhost 可能是 %但本地连接时仍会有差异把 host 设置成 localhost 即可。第四个高频错误是 404 页面出现 “HTTP Status 404 - /oldman/list” 而控制台没有报错这类错误多半是 Controller 类没有被 Spring MVC 容器扫描到。检查 spring-mvc.xml 的 component-scan base-package 是否包含了 controller 的包路径注意区分大小写且不能放在 applicationContext.xml 的扫描范围之外。最后一个坑是中文乱码。控制台打印中文乱码是 IDEA 的编码设置问题在 Help - Edit Custom VM Options 加-Dfile.encodingUTF-8。页面中文乱码是 JSP 页面编码不一致统一使用 UTF-8 声明。数据库中文乱码的修复方式是删除重建库表执行 ALTER TABLE xx CONVERT TO CHARACTER SET utf8mb4 不完整因为存量数据的连接参数错误会继续产生乱码。以上可以参考这些维度去检查 SSM 工程配合一个办法每次改动只动一个变量验证过了再动下一个。这个方法本身不是 SSM 专属但在类似 JSP、XML、Java 三处都要调整的场景里最有效也最适合毕业设计这种小步快跑的项目。本文还有配套的精品资源点击获取