简介一套面向SSM课程设计毕业设计场景的JSP社会保险管理系统项目包覆盖参保人员档案管理、保险金额缴纳、保险金发放、信息查询、缴纳信息发布与系统维护六项核心业务适合Java Web方向学生作为可运行参考工程。压缩包约24.47MB内置可运行源码、MySQL数据库文件及配套开发文档可直接导入IDE运行便于对照学习MVC分层、MyBatis持久层和JSP前端交互的实现方式。已有1802人学习下载资源围绕完整业务流程展开从档案增删改查到缴费审核、发放入账、补缴信息发布均有对应功能代码配合数据库脚本可快速还原系统环境降低课程设计与毕设起步门槛。文档有助于梳理需求分析与模块设计思路适合需要快速搭建社保管理类项目的初学者参考。1. 社会保险管理系统源码包先别急着双击用这张判断标准决定要不要花时间如果说“可运行”是一个源码包的基本底线那么在我接手过的十几个 SSM 老项目里真正能在 30 分钟内跑起来的其实不到一半。社会保险管理系统这个压缩包在标题里给自己贴了“可运行源码数据库文件文档”三个标签听起来很完整但标签从来不能当运行保障决定它价值的只有一件事你能不能在自己电脑上用最快速度把登录页打开。这套系统的典型落点是课程设计、毕业设计和内部练手项目业务模型是参保单位、参保人和缴费记录之间的几组基本关系数据量和并发都不大但对 Spring、Spring MVC、MyBatis 这三层框架的覆盖非常完整。适合三类人想把 SSM 串成一条完整链路去学习的初学者需要一个可演示项目去准备技术面试的求职者以及要接手旧版社保业务系统、准备做二次改造的开发人员。2. SSMMySQLJSP 这套组合为什么敢称“能跑”从框架分工到包内体检2.1 SSM 三件套各管一段Spring、Spring MVC、MyBatis 在社保场景里的分工社保系统的核心业务无非是参保登记、缴费记录、待遇申报和查询统计这些功能逃不开一套老组合Spring 容器管对象Spring MVC 管请求分发MyBatis 管数据库读写。在典型的 ssm 项目里Spring 的 IoC 容器负责把 Controller、Service、Mapper 三层的对象组装起来依赖关系用 XML 或注解声明Spring MVC 的 DispatcherServlet 负责接住浏览器发出的请求再按 URL 映射找到对应方法MyBatis 则把 Java 方法和 SQL 绑定到一起重点关注 SQL 的动态拼接和结果集映射。判断自己有没有看懂装配关系标准做法是顺着一次登录请求走JSP 页面提交用户名密码请求按 web.xml 里配置的 url-pattern 落到 DispatcherServlet再由 HandlerMapping 找到 LoginController 的 login 方法方法调 SysUserServiceService 里调 SysUserMapperMapper 执行 XML 里的 select 语句查出来的 SysUser 对象一层层传回最后放进 ModelAndView由 InternalResourceViewResolver 找到 /WEB-INF/jsp 下的登录结果页。这条数据流在社保项目里特别典型因为它的查询条件太多——参保状态、缴费年月、单位编号、险种类型——正好用 MyBatis 的动态 SQL 来拼条件。你在项目里看到 if test 和 where 标签就是干这个用的。2.2 为什么选 MySQL 而不是别的库为什么页面层还是 JSP选 MySQL 算是顺理成章。社保管理系统的表结构固定且关系清晰参保人、单位、缴费记录三张核心表加上若干字典表用 InnoDB 引擎足够支撑课程设计和中小型单位内部使用的并发量。它的运维成本也低Navicat 点几下就能把数据库文件导进去换个机器重新部署时只要 sql 文件还在就不会卡在库初始化上。反过来如果换成 Oracle 或 PostgreSQL驱动、方言、日期函数三关就能劝退大半想复现的人这和压缩包标题里宣称“可运行源码”的目标是冲突的。留用 JSP 也是同一个逻辑。SSM 全盛期的项目几乎没有前后端分离的形态JSP 可以在页面里直接用 JSTL 标签迭代 ListCtrlF 就能定位到某个字段到底在哪个表格里渲染。社保类系统交互不复杂主要是查询表单加数据表格服务端渲染的 JSP 在维护成本上甚至比 Vue 那套更省事。所以我常说看到这类项目先别急着批判“技术栈老旧”先想清楚它的业务和运行环境。在部署机器只有 Tomcat、没有 Node 环境的场合JSP 恰恰是最不需要额外伺候的选择。组件在社保系统里的职责常见替换方案为什么这里选它SpringBean 管理、事务、AOP 日志Spring Boot 的自动配置老项目标准组合文档最多Spring MVC页面路由、参数绑定、拦截器前后端分离用 REST 接口页面少、交互简单服务端渲染够用MyBatis动态 SQL、结果映射JPA / Hibernate查询条件多变动态 SQL 最顺手MySQL持久化存储Oracle / PostgreSQL成本低、导出导入方便JSP服务端渲染页面Vue / React无 Node 环境也能部署2.3 拿到压缩包后先做四次体检再决定从哪一步开始拿到标题里的 zip 后我不建议立刻解压丢进开发工具然后点启动而是先按下面四条把结构摸一遍。第一看文档。项目文档里通常会写明 JDK、Tomcat、MySQL 的版本匹配关系还会顺手给出默认登录账号密码。第二看 pom.xml 或 lib 目录。Maven 工程看 pom.xml 里 Spring、MyBatis 的版本和依赖是否完整非 Maven 工程看 lib 目录缺不缺 jar。第三看数据库文件。sql 文件是整库导出还是只含建表语句是否带初始化的系统管理员账号用的是 utf8 还是 utf8mb4这些直接决定导入方式。第四看 src/main/resources 下的属性配置文件记下里面配置的库名、用户名和密码后面导入数据库和修改连接时要对应上。这四步的顺序是刻意安排的。文档和 pom.xml 版本对不上等于在启动前就得到一个错误期望数据库字符集不清楚导完数据打开页面就是满屏乱码。我把这步叫黑匣子体检——在通电之前先把接线说明书查清楚。跳过文档直接启动是新手最容易犯的错也是“启动半小时排错两小时”最常见的源头。这类 Maven 工程的常见目录结构如下你可以拿解压结果比对少哪一块心里就有数项目名/ ├── pom.xml ├── src/main/java/... # Controller / Service / Mapper 源码 ├── src/main/resources/ │ ├── db.properties # 数据库连接参数 │ ├── spring/ # Spring 与 Spring MVC 配置 │ └── mapper/ # MyBatis 的 Mapper XML ├── src/main/webapp/ │ ├── WEB-INF/jsp/ # 页面文件 │ └── index.jsp └── sql/ └── social_security.sql # 数据库初始化脚本结构体检和版本核对做完后面启动的成功率会高很多。下一章按真实操作顺序把从环境准备到看到登录页的完整步骤过一遍。3. 本地复现 30 分钟环境匹配、数据库导入、配置修改和第一次启动3.1 环境版本匹配JDK、Maven、Tomcat、MySQL 的推荐组合SSM 项目的环境版本敏感度比 Spring Boot 高很多JDK 版本过高或 Tomcat 版本太新都会在启动阶段制造各种莫名其妙的报错。常见的可用组合如下表我建议在这个范围内选择别盲目用最新版。组件推荐版本说明JDK1.88u201Spring 5.x 官方支持避免直接用 17 / 21Maven3.6.x与开发工具自带 Maven 兼容Tomcat8.5 或 9.09 支持 Servlet 4.0够用MySQL5.7 或 8.0驱动版本要与数据库对应开发工具2020 年以后版本老接口也能正常导入先说为什么必须 JDK 1.8。Spring 5 在 JDK 8 上是最稳的而很多老项目用的还是 Spring 4.3 甚至更早那些版本在 JDK 11 以上会因为模块化限制直接报 IllegalAccessError。Maven 3.6 配合 JDK 8 也是当前兼容面最广的组合Maven 3.9 在个别老插件上会有警告不是不能用但没必要给自己加戏。Tomcat 8.5 和 9.0 都可以9.0 对 Servlet 规范支持更新但有些项目用了自定义的 servlet 依赖版本冲突时要回到 Tomcat 8.5 验证。动手前先检查当前环境三个命令就够了java -version mvn -v mysql --version执行后要能看到 JDK 1.8、Maven 3.6.x、MySQL 5.7 或 8.0。如果 java -version 显示的是 17 或 11建议先装一个 1.8而不是直接改项目去适配高版本——改 pom 和 Spring 配置的时间足够你重装完 JDK 了。3.2 数据库导入命令行导 SQL 的完整动作与字符集参数数据库文件是这套系统能不能跑起来的第二个关键点。sql 文件导入前先打开文件随便看前面 20 行目的是确认三件事有没有 CREATE DATABASE 语句、表名和字段有没有中文注释、文件头部有没有 SET NAMES utf8mb4 之类的字符集声明。确认后有两种导入方式。命令行最省事执行下面的命令mysql -uroot -p --default-character-setutf8mb4进入 MySQL 交互界面后再执行CREATE DATABASE IF NOT EXISTS ssm_social_security DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE ssm_social_security; SOURCE /path/to/social_security.sql;其中 --default-character-setutf8mb4 是保证中文字段名和注释不乱码的关键参数。如果你的 sql 文件里已经带了 CREATE DATABASE 语句并且库名和上面一致就不要再手动建库直接 USE 目标库再 SOURCE 即可。使用 Navicat 的另一个常规做法是右键“运行 SQL 文件”但注意 Navicat 默认连接字符集要选 UTF-8否则导入后表注释还是会出现乱码。提示导入完成后验证一下执行 SHOW TABLES 看核心表是否齐全再执行 SELECT * FROM sys_user LIMIT 5 看初始化账号是否在库里。如果 sys_user 为空说明这个 sql 文件可能只有结构没有数据你要么手动补一条管理员账号要么回头找完整的初始化脚本。3.3 必改配置数据库连接参数和视图解析器从 zip 解压出来的属性文件里数据库连接通常长这样拿它当模板改三处即可jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/ssm_social_security?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456先看 driver。MySQL 5.7 且 pom 里用的是 5.1.x 驱动时driver 是 com.mysql.jdbc.DriverMySQL 8.0 必须换成 com.mysql.cj.jdbc.Driver同时依赖版本升到 8.0.x否则启动即报 ClassNotFoundException。url 里的库名 ssm_social_security 要和导入时的库名完全一致password 换成你本机的 MySQL 密码。useSSLfalse 是为了去掉安全连接告警serverTimezoneAsia/Shanghai 是 MySQL 8 驱动连接时的必填参数少了它大概率报时区不识别错误。接下来看 Spring MVC 的视图解析器。项目里的 spring-mvc.xml 或 spring-web.xml 中通常有下面这段配置它决定了 JSP 页面放在哪里bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp/ / property namesuffix value.jsp / /bean改的重点是确认 prefix 和实际页面目录一致。如果页面在 src/main/webapp/WEB-INF/jsp 下这个配置就不用动如果页面被放到根目录的 jsp 文件夹里prefix 要改成 /jsp/。这里有个安全细节放在 WEB-INF 下的 JSP 不能被浏览器直接访问只能通过 Controller 转发这是老 SSM 项目常用的页面保护方式。3.4 第一次启动开发工具部署与手动部署到 Tomcat 的差异配置改完进入第一次启动。如果你用开发工具常见的做法是 Run/Debug Configurations 里新增一个 Tomcat Server LocalDeployment 里选择 war exploded然后修改 Application context 为 /ssm_social_security。这样启动后访问地址是 http://localhost:8080/ssm_social_security/login.jsp 或项目文档里给出的登录路径。Application context 必须和 zip 文档里的路径一致很多项目在 JSP 里用 ${pageContext.request.contextPath} 拼接绝对路径context 如果填错页面 CSS、表单 action 全部会错位。如果你不习惯集成环境部署也可以用 Maven 先打 war 包再扔进 Tomcatmvn clean package -DskipTests cp target/ssm_social_security.war $CATALINA_HOME/webapps/ $CATALINA_HOME/bin/startup.sh打包命令里加 -DskipTests 跳过测试打包完成后把 war 复制到 webapps 目录Tomcat 启动时会自动解压并部署。Windows 环境就用 startup.bat日志在 logs 目录下看Unix 环境用 tail -f logs/catalina.out 跟踪启动过程。第一次启动的验收动作其实就三个日志里出现 Started 或 Deployment of web application finished不报 Exception浏览器能打开登录页拿文档里的默认账号密码能登录进去。三件事都通过这个 zip 才真正称得上“可运行源码”。4. 代码走读一条社保缴费数据从 JSP 页面到 MySQL 的完整链路4.1 从 JSP 表单到 Controller一次登录请求的映射过程启动成功只是开始真正判断一个 SSM 项目能不能改、好不好接手的标准是你能否顺着一次请求把代码走通。我挑最典型的两个链路讲登录和缴费查询这两条链路几乎覆盖了 MVC 的全部要点。登录页的 JSP 表单通常长这样form action${pageContext.request.contextPath}/login methodpost input typetext nameusername placeholder账号 / input typepassword namepassword placeholder密码 / button typesubmit登 录/button /form表单提交后请求走到 ControllerController public class LoginController { Autowired private SysUserService sysUserService; RequestMapping(value /login, method RequestMethod.POST) public String login(String username, String password, HttpSession session, Model model) { SysUser user sysUserService.login(username, password); if (user ! null) { session.setAttribute(loginUser, user); return redirect:/payRecord/list; } model.addAttribute(errorMsg, 账号或密码错误); return login; } }几个关键点。第一表单里的 nameusername 和 Controller 方法参数 username 同名Spring MVC 会自动完成参数绑定所以改表单 name 时必须同步改方法参数。第二返回字符串 login 时InternalResourceViewResolver 会拼成 /WEB-INF/jsp/login.jsp这就是前面 3.3 节那个配置的用途。第三登录成功用 redirect:/payRecord/list 而不是直接 return list是为了避免刷新页面时重复提交表单。4.2 Service 层与 MyBatis事务、SQL 注入和动态查询登录逻辑在 Service 层的关键是密码处理和事务边界。我见过不少课程设计直接明文比对密码这不是不能跑而是实战习惯太差。正确的做法是统一用 MD5 摘要后比对如下面代码Service Transactional(readOnly true) public class SysUserServiceImpl implements SysUserService { Autowired private SysUserMapper sysUserMapper; Override public SysUser login(String username, String password) { String md5Password MD5Util.encode(password); return sysUserMapper.selectByUsernamePassword(username, md5Password); } }写三层代码时有个实际经验接口放方法定义实现类加 Service 和 Transactional。Transactional 默认只在抛出运行时异常时回滚社保业务里常见的场景是“单位变更同时更新参保状态”这类写操作必须放在一个事务方法里防止半个成功。查询类的读取操作可以标 readOnly true会稍微省一点锁开销。对应到 MyBatis 层Mapper 接口与 XML 是一一对应的public interface SysUserMapper { SysUser selectByUsernamePassword(Param(username) String username, Param(password) String password); }XML 里写select idselectByUsernamePassword resultTypecom.example.social.security.entity.SysUser SELECT id, username, password, real_name, role FROM sys_user WHERE username #{username} AND password #{password} /select这里要解释两件事。一是 #{username} 用的是预编译占位符可以防 SQL 注入如果你看到代码里用 ${username} 拼 SQL那是硬编码隐患动了输入框就可能有注入风险。二是 resultType 如果写了全限定类名也可以配置 typeAliasesPackage 把包名简化为 SysUser但全限定类名最保险复制粘贴不会踩雷。至于缴费明细这类列表页SQL 往往带复杂的动态条件select idselectPayRecordPage resultTypePayRecord SELECT id, user_id, pay_month, base_amount, status FROM t_pay_record where if testuserId ! null and userId ! AND user_id #{userId} /if if testpayMonth ! null and payMonth ! AND pay_month #{payMonth} /if /where /selectwhere 标签会自动去掉第一个多余的 ANDif 标签让查询条件可选。这就是 MyBatis 在社保系统里最不可替代的地方参保状态、缴费年月、单位编号的组合筛选是经常变化的SQL 不可能为每个组合写一条动态 SQL 是唯一省力的做法。4.3 社保系统的角色权限拦截器配合 Session 的常见写法SSM 老项目的权限控制大多不做成 Spring Security而是靠 Session 加拦截器。社保系统的典型角色有三种系统管理员负责账号和基础参数维护业务经办人员负责参保登记和缴费核定单位或个人用户只能查询自己的数据。这种权限模型用角色字段加拦截器就够了。拦截器最基础的功能是登录校验public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getSession().getAttribute(loginUser) null) { response.sendRedirect(request.getContextPath() /login); return false; } return true; } }注册拦截器时要注意把登录请求排除掉否则会形成死循环mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/static/**/ /mvc:interceptor /mvc:interceptorsexclude-mapping 是新手最容易漏的漏掉以后一旦 Session 过期登录页也会被拦截器挡下来然后一直重定向到 /login 出不来。业务上的角色细分往往就在页面层根据 loginUser.role 判断管理员看到“用户管理”菜单经办人员看不到这是最简单的菜单级权限做法。4.4 拿着表结构反推代码读一个 SSM 项目的最快路径走读代码时我习惯反向来先看数据库表再看 Mapper再看 Service最后看页面。社保系统核心表通常就是用户表、参保人信息表、缴费记录表、字典表这四类。看表能看出字段命名规律比如 create_time、update_time、real_name 这类下划线命名然后去 MyBatis 全局配置找有没有下面这个设置configuration settings setting namemapUnderscoreToCamelCase valuetrue/ /settings /configuration有这个设置create_time 能自动映射到实体类的 createTime 属性resultMap 都不用写。没有这个设置时就只能在 Mapper XML 里靠 resultMap 手动逐列映射。判断项目代码质量第一眼就看这个用 resultMap 还是驼峰自动映射基本能看出原作者的熟练程度。反推路径的实操办法是在页面里搜索 ${record.payMonth}找到这个字段名后去对应的实体类和 resultMap 里看列名再顺着列名去表结构确认业务含义。这样一个字段从显示到存储的链路就通了改需求时你知道要动哪一层。链路走完一遍这个项目的代码结构在你眼里就不再是黑匣子。5. 避坑排查SSMMySQLJSP 项目启动和二次开发的 5 个故障案例这章写我在复现和改造 SSM 社保系统时实际遇到的套路化问题每条按“现象、原因、解决”的结构写你自己对照排查即可。5.1 现象Tomcat 启动报 NoClassDefFoundError页面始终打不开最典型的就是 org.springframework.web.context.ContextLoaderListener 这个类找不到或者某个 Mapper 接口的类文件缺失。原因通常是 Maven 依赖没有完整下载本地仓库里有 .lastUpdated 的损坏缓存非 Maven 项目则是 WEB-INF/lib 里少拷了 jar。解决方式是先清包重下依赖mvn -U clean package -DskipTests-U 强制刷新远程仓库能解决大多数“本地缓存了坏 jar”的问题。如果还报错再执行 mvn dependency:tree 看依赖树里缺什么重点检查 spring-webmvc、mybatis、mysql-connector、jstl 这四组依赖是否都在。血泪经验不要顺手打开开发工具的离线模式离线模式只会让报错换一种姿势出现根因还在本地依赖缺失。5.2 现象启动时报 Communications link failure 或 Access denied for user出现 Communications link failure第一步先用 Navicat 或命令行直接连 MySQL能连上说明问题在项目配置连不上说明 MySQL 服务本身没起来或者端口被防火墙挡了。原因还有两类一是 db.properties 里的 url 写的是 localhost 但 MySQL 只监听了某个绑定地址二是 MySQL 8 的驱动和 5.x 驱动混用。Access denied 则是用户名密码错误或者是 root 用户只授权了 localhost你却在另一台机器上连。解决时按下面几步检查先 telnet 127.0.0.1 3306 看端口再确认 MySQL 的 user 表里 root 的 host 是 localhost 还是 %再检查 db.properties 里的 driver 和 pom.xml 里的依赖版本。连接地址我建议直接写 127.0.0.1别写 localhost因为某些 JDBC 版本对 localhost 解析会走 IPv6导致连不上。5.3 现象登录后中文字段全是乱码或问号乱码问题百分之八十发生在数据库连接参数和 JSP 页面编码不一致。现象是页面里的“缴费基数”“参保单位”变成问号或者数据库客户端里看正常、页面里看乱。原因是 URL 里的 characterEncoding 没设置JSP 页面没有声明 UTF-8或者 MySQL 表结构本身是 latin1。解决思路是从上到下统一filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping这个过滤器要放在 web.xml 的最前面forceEncoding true 表示请求和响应都强制用 UTF-8。与此同时JSP 文件第一行保持 % page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8%数据库连接串保留 characterEncodingutf8三处一致乱码基本消失。5.4 现象登录页能打开但 css、js 全部加载失败页面裸奔、没有样式一般是两件事路径写错或静态资源被 Spring MVC 拦截。先按 F12 看 Network 里 css 请求返回的状态码如果 URL 里缺了项目名多半是 jsp 里写死了 /css/main.css 而没有加 ${pageContext.request.contextPath}。如果路径里有项目名但返回 404多半是 Spring MVC 的拦截器把 /static/** 当成 Controller 路由处理了。在 spring-mvc.xml 里加静态资源映射是最直接的解决mvc:resources mapping/static/** location/static/ / mvc:default-servlet-handler /还不行就把 DispatcherServlet 的 url-pattern 从 / 改成 *.do只拦动作请求静态资源交给 Tomcat 默认的 DefaultServlet 处理。改完后重启再刷新页面看样式是否恢复。5.5 现象导入 SQL 文件报错或部分表导完页面提示表不存在最常见的原因是 sql 文件里的库名和你建库的库名不一样或者文件里带视图、触发器导入顺序一错就中断。先做两件事用文本编辑器把 sql 文件转成 UTF-8 无 BOM 格式导入时显式指定字符集mysql -uroot -p --default-character-setutf8mb4 /path/to/social_security.sql如果报错信息里提到视图或存储过程说明文件里带了对象定义不能用 Navicat 的“运行 SQL 文件”整体跑要拆开执行先把建表语句跑完再单独跑视图那块。导完之后再用 SHOW TABLES 核对表清单确认新库里的表和文档里的表结构一致。保险起见我会导出前先在源库里做一次备份导出时勾选“包含 DROP TABLE”和“包含字符集设置”这样换个环境导入时不会因为残留表冲突而卡住。6. 进阶验证给缴费列表加一个字段用一次小改造看懂整条链路6.1 改造演示让“缴费状态”从数据库一路显示到 JSP跑通和看懂之间还差一次改造。我推荐的最小改造是在缴费明细列表页增加一列“缴费状态”数据来自 t_pay_record 表的 status 字段。改动点会覆盖 Mapper XML、实体类、JSP 三个位置正好可以检验 4.2 节里讲的链路是否真的被你掌握了。第一步确认 Mapper 的查询语句里 status 字段已经在 select 列表里。如果没查出来补上select idselectPayRecordPage resultTypePayRecord SELECT id, user_id, pay_month, base_amount, status FROM t_pay_record /select第二步在 PayRecord 实体类里补 status 属性及其 getter/setter。如果没配 mapUnderscoreToCamelCase还要在 resultMap 里加 status 列映射。第三步在 JSP 的表格里加一列把数字状态翻译成业务含义td c:choose c:when test${record.status 1}正常缴费/c:when c:when test${record.status 0}暂停/c:when c:otherwise终止/c:otherwise /c:choose /td改完重启打开缴费明细列表页确认新列出现在表格里。再做一次验证直接到数据库里执行 UPDATE t_pay_record SET status 0 WHERE id 1;然后刷新页面看那一条记录的“缴费状态”从“正常缴费”变成“暂停”。如果页面能跟着变说明整条请求链路是通的如果不变那就是查询条件命中了缓存或 JSP 没重新编译要回到 Tomcat 的 work 目录清掉旧页面缓存。这个练习我每次接手旧项目都会做一遍。第一次从别人手里接 SSM 老系统时我一度以为启动成功就算看懂项目后来才发现能改一个字段并且让页面、数据库、日志三方联动才叫真正掌握了这套代码。建议你也不要只停留在“跑起来”花半小时做掉这个小改造比再读十篇框架教程都管用。希望帮到你。本文还有配套的精品资源点击获取