1. 这套学生事务处理系统的真实定位:不是功能堆砌,而是SSM工程的完整标本
先说个我自己的观察。我在不少高校的课程设计和毕业设计群里泡过,发现一个很有意思的现象:每年都有一批学生拿"XX管理系统"当题目,需求文档写得五花八门,但最后落地的时候,代码质量和工程规范程度差距巨大。有的项目能跑,但一打开源码,Controller里写SQL,JSP里写业务逻辑,Service层形同虚设,整个一套“万能Java文件”;有的项目则像样的多,三层结构清清楚楚,配置链完整,扩展起来省心得多。
基于SSM的学生事务处理系统,属于后者这个梯队。SSM就是Spring + SpringMVC + MyBatis,这套组合在现在虽然不如Spring Boot那么“开箱即用”,但恰恰因为它的配置是显式的、非自动化的,反而特别适合用来理解Java Web开发的分层思想。
这套系统能做什么?核心就是学生日常事务的在线化管理。往细了说,一般包含这几块:
- 学生端:账号登录、个人信息维护、提交事务申请(比如请假、调课、在校证明开具)、查看申请审批进度
- 管理员/辅导员端:处理学生提交的申请、发布公告通知、管理学生档案、按条件检索学生事务数据
- 公共功能:登录拦截、会话管理、公告列表展示
换句话说,它不是要模仿OA系统那种复杂流程引擎,而是把一个典型的高校学生事务场景浓缩成一个可演示、可答辩、可二次开发的完整工程。
适合谁来看这篇内容?第一类是正在做课程设计或毕业设计的学生,手里刚拿到一份SSM学生事务处理系统源码,不知道从哪里下手;第二类是初学者,想把SSM的三层协作搞清楚,拿一个真实系统当教科书;第三类是打算基于这种老项目做重构或迁移的开发者,想快速摸清楚SSM工程的命脉在哪。
这套项目最宝贵的地方在于:它把“源码 + 文档 + 调试”三件事焊在了一个场景里。你拿到的不只是几个能跑的页面,而是一条从需求建模、数据库设计、后端分层到联调排错的全链路经验。下面我把这套东西拆开来讲,重点放在那些网上教程里查不到、但实际动手时一定会撞上的细节上。
2. SSM三件套的分工,以及一次事务请求的完整旅程
2.1 三个框架到底各管什么
很多初学SSM的人最大的困惑是:Spring、SpringMVC、MyBatis这三个框架,名字都听过,但真到看代码的时候,总觉得它们互相“缠”在一起。其实用一句话就能说清楚分工:
- Spring是“大管家”,负责管理对象。所有Controller、Service、Mapper对象的创建和依赖注入,都由Spring的IoC容器统一管理,这叫控制反转;对象之间怎么互相配合,由Spring的AOP(面向切面编程)来织入,典型应用就是事务管理。
- SpringMVC是“前台客服”,负责接收HTTP请求。浏览器的每一次访问、每一个表单提交,SpringMVC负责把请求路径映射到具体的方法上,把参数绑定到Java对象里,等业务逻辑处理完之后,再决定返回哪个视图(JSP页面)或者JSON数据。
- MyBatis是“数据搬运工”,负责和数据库打交道。它把SQL语句写在Mapper的XML文件里,通过接口方法绑定,把数据库表记录映射成Java实体类,也把Java对象转换成SQL参数。
用一个生活化类比:Spring是公司的行政部门,决定每个岗位配谁、谁向谁汇报;SpringMVC是前台接待,帮你找到对应的部门负责人;MyBatis是仓库管理员,你告诉他要什么货,他去仓库(MySQL)里给你翻出来换成标准包装(Java对象)。
2.2 一次“提交请假申请”在代码里到底经历了什么
为了让你看源码时不被绕晕,我以“学生提交请假申请”这个最常见的功能为例,把完整的调用链走一遍。
步骤大致是:学生登录后,在JSP页面填写请假表单,点击提交;浏览器发送一个POST请求,URL大概是/apply/submit;SpringMVC的DispatcherServlet收到请求后,根据@RequestMapping("/apply/submit")找到ApplyController里的submit方法;Controller调用ApplyService的submitApplication方法,把表单数据封装成ApplyRecord对象;Service层里如果有涉及金额、状态变更或多个表的写操作,会被Spring的事务管理器拦截,保证要么全成功、要么全回滚;Service再调用ApplyMapper接口,而ApplyMapper对应的ApplyMapper.xml里写好了insert into t_affair_apply(...) values(...);MyBatis执行SQL,把ApplyRecord对象的属性一一绑定到#{}占位符上;数据库写入成功后,控制权一层层返回,Controller把结果塞进Model,跳转到“提交成功”页面。
这条链路,对应到源码里的目录和文件,其实就是你接下来要重点阅读的骨架。我建议你拿到项目后,先不要一个个文件挨着看,而是按照“浏览器发出请求 → Controller → Service → Mapper → 数据库”这条主线去读,读懂了这条主线,其余功能都是这个主线的变体。
2.3 事务管理的边界在Service层,别和MyBatis搞混
项目文档里经常会提到“事务”,很多初学者容易误解成MyBatis管事务。实际上,SSM里事务管理的主人是Spring,而且是基于AOP切面来做的。
常见的配置是在spring-service.xml或applicationContext.xml里这样声明:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="select*" read-only="true"/> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>这段配置的意思是:凡是Service里以add、update、delete开头的方法,执行时开启数据库事务,如果中途抛异常就整体回滚;以select开头的方法,只读不开写事务。你拿到源码后,可以先去applicationContext.xml里找这段配置,确认一下事务切面覆盖了哪些方法命名模式。很多SSM系统的bug(比如数据写了一半、留下脏数据),往往就是事务配置没覆盖到位导致的。
3. 打开源码后,按什么顺序读才不迷路
3.1 先看配置文件,再看代码,最后看页面
一个SSM工程的目录结构通常长这样:
src/main/java ├── com.student.affair │ ├── controller │ ├── service │ │ └── impl │ ├── mapper │ ├── entity │ ├── utils │ └── interceptor src/main/resources ├── jdbc.properties ├── spring-mvc.xml ├── spring-service.xml ├── mybatis-config.xml └── mapper └── ApplyMapper.xml src/main/webapp ├── WEB-INF │ └── web.xml ├── static ├── jsp │ └── student │ └── admin pom.xml第一次接触SSM的人,很容易一头扎进controller包开始看Java代码,结果越看越乱。我的建议是倒过来:先读pom.xml看依赖版本,接着读web.xml这个入口,再读三个Spring/MyBatis配置文件,然后读jdbc.properties,最后才去读Java代码。
为什么这么安排?因为SSM是配置驱动的架构,你在web.xml里能看到Spring的监听器在哪个阶段启动,能看DispatcherServlet拦截什么URL;在spring-service.xml里能看到Spring容器扫描哪些包;在spring-mvc.xml里能看到SpringMVC扫描哪些包;在mybatis-config.xml里能看到Mapper XML的扫描路径。这些“骨架”定了,Java代码里的注解才能跑通。很多启动报错,本质上是配置链断裂——某个bean没被扫描到、某个Mapper文件没加载进去,而不是Java代码本身写错了。
3.2 核心Java代码:三层各自的关注点
- entity包:对应数据库表的实体类,字段名尽量和表字段一致或使用驼峰映射。你看这里的代码时,重点是理解数据库表结构,不需要纠结逻辑。
- controller包:关注的是
@RequestMapping的路径值、方法参数的绑定方式(@RequestParam、@PathVariable、Model),以及return的视图路径。这里是最容易看出系统功能列表的地方,一个方法对应一个接口动作。 - service包 + impl包:这是业务逻辑的重心。真正“值钱”的代码在impl里,比如审批状态机的流转判断、权限校验、多表联合查询后的数据组装。我一般建议读者用调试器在Service层的核心方法上打断点,因为这里是最能理解业务规则的地方。
- mapper包:接口方法名要和XML里的
statementid一一对应。MyBatis框架本身不检查这个对应关系,是运行时才报错的,所以这里出问题往往最隐蔽。
3.3 SSM常用注解,一张表对照清楚
结合这套事务处理系统的源码,我把出现频率最高的注解整理如下:
| 注解 | 作用 | 在系统中的典型位置 |
|---|---|---|
@Controller | 标记类是SpringMVC控制器 | 类名标注,如ApplyController |
@RequestMapping | 映射URL到方法或类 | 方法上标注路径,如/apply/submit |
@Autowired | 依赖注入,按类型自动装配 | Controller里注入Service,Service里注入Mapper |
@Service | 标记业务层组件,交给Spring管理 | 标注在Service实现类上 |
@Repository | 标记持久层组件 | 标注在Mapper接口上,让Spring能够扫描到 |
@RequestParam | 将请求参数绑定到方法参数 | 接收表单单个参数 |
@ResponseBody | 返回值直接写回浏览器,不经过视图解析 | 常用于返回JSON给前端 |
@PathVariable | 把URL模板变量绑定到方法参数 | 如/detail/{id} |
这里特别提醒一个细节:@Repository和@MapperScan并不冲突。你可以在配置类里用@MapperScan("com.student.affair.mapper")批量扫描Mapper接口,也可以在每个Mapper接口上加@Repository并配合Spring的包扫描。拿到源码后,先确认项目用的是哪种方式,然后找到对应的“入口”,不然你可能会遇到“明明写了接口,但启动时还是提示Mapper bean找不到”的诡异问题。
4. 环境准备与部署调试:从零到把系统跑起来
4.1 版本匹配是第一生命线
SSM本身没有强制版本,但是不同版本的Spring、JDK、Tomcat、MyBatis之间是有兼容性讲究的。我实测过的稳妥组合是:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 | SSM老工程最稳妥的版本,兼容性最好 |
| Maven | 3.6.3 | 3.8以上有时会遇到仓库配置问题 |
| Tomcat | 8.5.x | 适配Servlet 3.x,支持注解扫描 |
| MySQL | 5.7.x | 性能稳定,utf8mb4支持良好 |
| Spring/SpringMVC | 4.3.x 或 5.0.x | 和JDK 8配合成熟 |
| MyBatis | 3.4.x | 经典稳定版本 |
| 数据库驱动 | mysql-connector-java 5.1.47 | 注意5.1和8.0的驱动类名不同 |
4.2 数据库初始化:别用记事本直接复制粘贴
这类系统一般会附带一个.sql脚本,通常是student_affair.sql。很多人在导入时翻车,表面原因千奇百怪,根源基本都是两个:字符集不对、版本不兼容。
我的建议是:在Navicat或DataGrip里新建一个连接,再新建数据库,命名为和jdbc.properties里jdbc.url的数据库名一致。建库语句可以写成:
CREATE DATABASE IF NOT EXISTS student_affair DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE student_affair;然后选择运行SQL文件,目标数据库选student_affair,运行前确认一下SQL文件开头是不是有CREATE DATABASE,如果有,你就需要手动把那一行去掉,否则可能重复建库报错。
导入完成后,务必做一次抽查:在关键表(比如student_info、t_affair_apply)里Select出几条数据,确认中文没有变成乱码。这个坑一旦留下,后面排查起来极其恼火,因为页面乱码、SQL查询条件不匹配,源头都可能是这一环。
4.3 IDEA中部署并启动的完整步骤
假设你用的是IDEA,拿到源码后按这个顺序走一遍会比较稳:
- 使用IDEA的
Open直接打开项目根目录,等Maven自动导入依赖。如果右下角提示Auto-Import,直接开启。 - 打开
File > Project Structure,确认Project SDK是1.8,Language Level设为8。 - 打开
Settings > Build Tools > Maven,确认Maven home path和settings.xml指向你本地的仓库配置,不然后面依赖下载会卡死。 - 打开
jdbc.properties,检查jdbc.url=jdbc:mysql://localhost:3306/student_affair?useUnicode=true&characterEncoding=utf8&useSSL=false,用户名和密码改成你本机MySQL的。 - 配置Tomcat:打开
Run > Edit Configurations,左侧点加号,选Tomcat Server下的Local。在Deployment标签页点加号,选Artifact,把StudentAffair:war的包加进去,Application Context建议填/。 - 启动。如果正常,IDEA控制台会打印Spring容器初始化的日志,看到
Initializing Spring root WebApplicationContext这一行后面没有ERROR,就说明容器起来了。
4.4 热部署要不要开
我个人的态度是:SSM这类老工程,不要过度依赖热部署插件。因为Spring的bean实例和Tomcat的类加载器机制在里面容易出幺蛾子,经常出现“改了代码没生效”或者“热部署后ApplicationContext重复初始化”的问题。如果你用了JRebel或DevTools,遇到诡异问题第一反应先看看是不是热部署引起的。调试时干脆用IDEA自带的Rerun重启一下Tomcat,几秒钟的事,比被坑了一个小时划算太多。
5. 调试排错:这套系统跑起来之后,最常见的五个坑及完整排查链路
5.1 数据库连接失败:先分清楚是驱动、地址、还是账号问题
这个错误几乎是每个人都会遇到的。控制台报错形如:
Cannot create PoolableConnectionFactory Access denied for user 'root'@'localhost'这句大白话是:MySQL拒绝了你的密码。这时候不要急着改代码,按这个顺序排查:
- 检查
jdbc.properties里的username/password,注意有没有不可见字符(复制粘贴时的空格非常阴险)。 - 检查
jdbc.url里的localhost能不能从你机器上正常访问MySQL。命令行敲mysql -u root -p先试一下。 - 检查MySQL的驱动类。如果你是
mysql-connector-java 5.x,驱动类是com.mysql.jdbc.Driver;如果是8.x,则是com.mysql.cj.jdbc.Driver。驱动类写错,报错信息会直接告诉你ClassNotFoundException。 - 检查
useSSL=false这个参数。很多新版本MySQL(或MariaDB)在用5.1驱动时会因为SSL握手问题报Communications link failure,显式关闭即可。
还有一个藏在深处的坑:MySQL 8.0以上默认使用caching_sha2_password认证插件,老驱动根本不认识,报错看起来和密码错误一模一样。如果确认密码没问题,那就要考虑驱动和MySQL版本不匹配,要么升级驱动到8.x,要么把MySQL用户改回mysql_native_password。
5.2 编译通过,但访问页面一直404
系统能启动,说明Spring容器没问题;页面404,问题大概率出在URL映射或视图路径上。
排查时,第一件事是看访问地址和Controller上写的@RequestMapping是否完全一致,包括大小写。/Apply/submit和/apply/submit是不同的,这是新手最容易忽略的。
第二件事是看web.xml里DispatcherServlet的url-pattern。如果配置是/,表示SpringMVC接管所有的请求;如果配置是*.do,那你所有的URL都必须以.do结尾。很多老项目模板喜欢用*.do的风格,而源码里的JSP表单提交地址写的却是没有.do的路径,这就直接404。
第三件事是看spring-mvc.xml里的视图解析器配置:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>Controller里return "student/applyList"会被解析成/WEB-INF/jsp/student/applyList.jsp。如果你把JSP放错目录,或者少了某个子目录,页面就会404,但Tomcat日志里不一定有明显的ERROR信息,只有一行Not found,这种时候就要去核对前缀和后缀。
5.3 页面能打开,但提交表单时500:这个错误才是真的主机游戏
能启动、能打开页面,说明静态资源和JSP渲染没问题。但表单一提交就500,这类问题我在学生的项目里看得最多。报错堆栈往往很可怕,一大片,但关键信息通常在前三五行的Caused by里面。
最常见的原因是MyBatis的Mapper绑定异常,报错长这样:
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.student.affair.mapper.ApplyMapper.insert这个错误的意思是:ApplyMapper接口里定义了insert方法,但是MyBatis在ApplyMapper.xml里找不到对应的statement id。原因有几种:
- XML文件的namespace写错,没有指向
ApplyMapper这个接口的全限定名 - XML文件没有放在
mybatis-config.xml里配置的mapperLocations路径下 - XML文件放在
src/main/resources/mapper/下,但没被Maven扫描进classpath
验证方法很简单:打开项目target/classes目录,看看ApplyMapper.xml有没有被复制进去。如果没有,那就是Maven的资源扫描没覆盖,需要在pom.xml的build节点里,把resources目录显式加进去。这类问题,IDE不报错,编译不报错,运行到绑定方法才炸出来,防不胜防。
5.4 中文乱码:从浏览器到数据库的整条链路排查
乱码属于“看着不致命,但极其恶心”的问题。完整链路有四个环节:JSP页面编码、请求编码、响应编码、数据库字符集。
你打开JSP文件,第一行应该长这样:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>web.xml里要配一个编码过滤器,把请求和响应统一成UTF-8:
<filter> <filter-name>encodingFilter</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>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>数据库表和数据本身也要是utf8mb4,建表语句没有带charset的话,可以在MySQL里执行:
ALTER TABLE student_info CONVERT TO CHARACTER SET utf8mb4;这几个都确认之后,乱码大概率就是彻底解决了。如果你在JSP页面和数据库之间还看到乱码,可以再检查一下jdbc.url里有没有characterEncoding=utf8,这一步是让JDBC连接层面也使用UTF-8传输。
5.5 调试技巧:日志、断点、SQL输出三管齐下
给SSM项目调试,我强烈建议把MyBatis的SQL日志打开。在log4j.properties里加一行:
log4j.logger.com.student.affair.mapper=DEBUG这个配置只对Mapper包生效,控制台会打印每次执行的SQL、绑定参数和返回结果数量。这比你自己猜SQL对不对要高效一百倍。
断点调试的话,核心断点位置我推荐三个:Controller的方法入口(看请求参数是否进来了)、Service实现类的核心方法(看业务状态如何流转)、Mapper接口调用(看SQL参数和返回值)。在IDEA里,调试模式启动Tomcat后,请求进入断点时可以看到完整的调用栈,顺着栈往下走,你就能看到SpringMVC是怎么把请求一步步传到Mapper的,这条栈本身就是最好的源码地图。
6. “文档”不是走过场:怎么把源码提炼成一份真正能加分的项目报告
6.1 文档的标准骨架
这类系统的说明文档,不需要写得像产品需求书那么宏大,但一定要覆盖这五个部分:
- 需求分析:用了哪些角色,每个角色的核心需求是什么
- 系统设计:模块划分、三层架构说明、核心流程的描述
- 数据库设计:表的职责说明、表之间的关系、关键字段的意义
- 模块实现:每个功能对应的Controller方法、Service方法、Mapper XML的对应关系
- 测试与调试:你的运行环境、测试用例、遇到并解决的典型问题
6.2 从源码逆向生成文档的技巧
很多人觉得写文档比写代码还难,其实那是因为你试图凭空写。正确的做法是“看代码写文档”,按三层走读:
- 读
web.xml和配置文件,画出系统运行环境图,这部分回答“系统怎么启动的” - 读Controller层所有
@RequestMapping,整理出接口清单,这部分回答“系统提供哪些功能” - 读Service impl层核心方法,找出方法间的调用关系,这部分回答“业务规则怎么流转”
- 读数据库表设计,结合Mapper XML里的SQL,整理出业务流程需要哪些数据表一起协作
给一个例子:如果文档里要写“学生提交请假申请的时序描述”,你不用画花哨的时序图,只需要用文字描述调用链,按照“JSP提交→DispatcherServlet→ApplyController.submit→ApplyServiceImpl.submitApplication→ApplyMapper.insert→数据库→返回结果”这个顺序写清楚,再加上异常处理逻辑,导师看了就知道你是真正理解了系统,而不是抄的。
6.3 答辩时的高频问题,提前在文档里埋好答案
我参与过一些项目答辩的旁听,老师最爱问的问题无非是这几个:
- 为什么用SSM而不是Spring Boot?答案不是“因为题目要求”,而是SSM配置显式、分层清楚,适合理解底层原理
- 你怎么保证数据一致性?这时就该提Spring的事务管理配置
- 如果学生和管理员同时操作同一条记录怎么办?这就涉及到数据库锁和事务隔离级别
- 系统能承载多少并发?老实的回答是:这种课设级系统并发能力有限,但可以从连接池配置和SQL优化角度聊改进思路
提前把这些问题的回答框架写进文档,答辩的时候不会慌。
7. 二次开发实战:怎么在不破坏现有体系的情况下新增一个“宿舍报修”模块
拿到这套系统之后,很多人都会面临同一个问题:功能不够,想加一个自己的模块。我以“学生宿舍报修”为例,走一遍最小化新增流程。
7.1 数据库层:先建表,再建实体
新建表:
CREATE TABLE repair_record ( id INT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(20) NOT NULL, content VARCHAR(500), status TINYINT DEFAULT 0 COMMENT '0-待处理 1-处理中 2-已完成', create_time DATETIME );同步在entity包下新建RepairRecord实体类,字段和表保持一致,日期类型用java.util.Date。
7.2 Mapper层:接口加XML
Mapper接口里写方法:
@Repository public interface RepairMapper { int insertRecord(RepairRecord record); List<RepairRecord> selectByStudentId(String studentId); }XML文件命名RepairMapper.xml,namespace写成com.student.affair.mapper.RepairMapper。在MyBatis配置或Mapper扫描配置里确认这个XML会被加载到。如果你不确定加载路径,可以在mybatis-config.xml的<mappers>节点下加一行:
<mapper resource="mapper/RepairMapper.xml"/>7.3 Service层:把事务边界划清楚
新建RepairService接口和RepairServiceImpl实现类,核心方法上加@Transactional注解。这里注意,如果用注解式事务,要在spring-service.xml里开启注解驱动支持:
<tx:annotation-driven transaction-manager="transactionManager"/>这个开关很多模板默认是关的。你只是继承项目里已有的事务习惯也行,如果原有项目用的是XML方式声明事务,那就继续在XML里加<tx:method name="add*"/>之类的配置,两种风格混用容易产生玄学bug。
7.4 Controller层:写接口
@Controller @RequestMapping("/repair") public class RepairController { @Autowired private RepairService repairService; @RequestMapping("/submit") public String submit(@RequestParam("content") String content, HttpSession session) { String studentId = (String) session.getAttribute("loginUser"); repairService.submitRe(parentRecord, studentId, content); return "redirect:/repair/myList"; } }这里注意一个SSM的老规矩:JSP跳转和重定向的写法。return "repair/myList"走视图解析器,return "redirect:/... "则是直接发一个重定向响应。表单提交后,推荐用重定向方式跳转,避免刷新页面时重复提交表单。
7.5 前端页面的配合
JSP页面里,表单的action路径要写带上项目上下文。如果你在Tomcat的Deployment里把Application Context设成了根路径/,那action可以写/repair/submit;如果没设置,而是叫StudentAffair_war_exploded,那所有路径都要带上这个前缀。很多人页面点了没反应或者404,根源就在这个上下文路径上。你可以用${pageContext.request.contextPath}动态拼接,这是最稳妥的写法:
<form action="${pageContext.request.contextPath}/repair/submit" method="post">8. 关于权限控制、数据权限和系统演进的一些经验
8.1 登录拦截别只写在Controller里
学生事务处理系统里,学生和管理员的权限明显不同。很多源码会在Controller里写:
if (session.getAttribute("loginUser") == null) { return "redirect:/login"; }这个方法对页面少的时候可行,但页面一多,每个方法都加一遍,迟早漏掉一个。更靠谱的方式是用SpringMVC的拦截器,在配置文件里注册一个登录检查拦截器,统一拦截未登录请求。实现HandlerInterceptor,在preHandle方法里判断Session,如果没登录就重定向到登录页。
8.2 数据权限比功能权限更隐蔽
“学生只能查自己的申请记录”这个需求,如果只在前端JSP里通过隐藏字段或URL参数控制,那这个系统就是纸糊的安全。正确做法是在Service层强制绑定当前登录用户的信息。你可以从Session中取出当前学生ID,然后作为查询条件传入Mapper,而不是信任页面传来的studentId参数。别人把URL里的studentId改成别人的学号,结果看到别人的数据,这种漏洞在答辩时被指出来,非常扣分。
8.3 从SSM走向Spring Boot的迁移思路
如果你后续想把这个项目升级成Spring Boot版本,替换工作其实是高度可复用的:entity、Mapper接口和XML基本不用大改,Service层几乎可以原封不动搬过去,主要变化集中在Controller层的注解和配置方式。把@Controller换成@RestController(接口化改造),把XML配置换成Java配置类和application.yml,把web.xml里的Filter和Servlet映射换成对应的Configurer或者注解。这套迁移思路,你只需要熟悉Spring Boot的基础,就能流畅地做下来。
说到底,SSM这套体系虽然“老”,但它把Web开发的那些底层概念——请求映射、容器托管、ORM映射、事务边界——都以一种非常显性的方式暴露在你面前。把这样一个学生事务处理系统的源码吃透、跑通、改出自己的功能,你对Java Web的理解,就已经过了“只会写CRUD”的坎,开始有工程化的感觉了。