ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SSM+B/S架构实战:高校学生管理系统从建表到部署

2026/9/13 10:05:22 拓冰建站 浏览量
SSM+B/S架构实战:高校学生管理系统从建表到部署 简介一套基于SSM框架的高校学生管理系统项目适合计算机相关专业毕业设计、课程实训以及需要快速上手Java Web开发的初学者。系统内置管理员、教师、学生三个角色功能覆盖学生请假审核、课表信息管理、院系班级维护、奖惩记录、学生成绩以及培养计划等核心业务流程完整前端采用Vue构建后端由Spring、SpringMVC、MyBatis与MySQL提供支撑。压缩包共1045个文件大小约40.44MB包含119个Java源文件、92个Vue组件、324个SVG图标、58个XML映射与配置、44个JAR依赖、4个SQL数据库脚本等源码与资源分类清晰便于按模块阅读和二次扩展。将项目导入IDEA或Eclipse并执行SQL脚本后即可运行对需要完成高校学生管理类毕设的同学而言这套系统提供了可落地的完整工程基础与文档参考已有52人学习下载。1. 为什么 2024 年了还在用 SSM 写高校学生管理系统Spring Boot、Spring Cloud 满地走的当下看到“基于SSM框架实现的高校学生管理系统”这个标题第一反应可能是“都什么年代了还在搞 XML 配置”。但如果翻过几套学生管理系统源码就会发现SSM 这套组合依然是课程设计、外网小项目和 java 面试题里出现频率最高的技术栈。原因很直接它把框架自动完成的事情重新摊开了——数据源怎么交给 MyBatis、事务边界划在哪、HTTP 请求如何被 DispatcherServlet 分发。高校学生管理系统这个业务域数据模型经典需求扁平正好用最朴素的方式把 B/S 架构从浏览器到数据库的整条链路跑一遍。这篇文章按“架构取舍→建表→SSM 整合→打包部署→验证排错”的顺序给出一套可复现的方案在校生能照着做工作三五年的开发者也能从中找回框架底层的肌肉记忆。2. SSMB/S 架构一次“查学生”请求是怎么串起来的2.1 B/S 架构这条请求链路上有哪几层B/S 架构Browser/Server的本质是“浏览器只负责渲染业务逻辑全在服务器端”。一次普通的“按学号查学生”请求在 SSM 项目里会按顺序穿过下面这些环节环节组件职责1浏览器发 HTTP 请求渲染返回的 JSP/HTML2Tomcat接收 Socket 连接匹配上下文路径与 Servlet3DispatcherServlet拦截所有进入的请求找对应的 Controller 方法4Controller解析参数、校验格式、决定调用哪个 Service5Service业务规则、事务边界Transactional6MyBatis Mapper生成 SQL、执行 JDBC、结果映射成 Java Bean7MySQL返回结果集在这个结构里每一层都不知道上一层的存在只面向接口编程。B/S 架构的好处是客户端零安装部署端只需要 JDKTomcat数据库这也是这类系统常被做成毕业设计形态的原因。2.2 为什么选 SSM 而不是直接上 Spring BootSpring Boot 省事的代价是“看不见”内嵌 Tomcat、自动装配、JAR 包一条命令启动。这些放在生产项目里是优势但放在一个用于学习和面试的高校学生管理系统里恰恰要的是把每根线头亮出来。SSM 的典型形态是Spring 管 Bean 与事务SpringMVC 管 HTTP 路由MyBatis 管 SQL 映射三者通过 web.xml 和两份 XML 配置手动粘合。把这三份配置搭通你才算真正理解 spring-boot-starter-web 在背后干过什么。java 面试题里“SSM 整合原理”一直被问就是在考这段不透明但绕不开的手动装配过程。我一般不会劝人在正式新项目里再手动搭一次 SSM但学习路线和课程设计这个阶段它比直接照抄一个 Spring Boot 项目有信息量得多。2.3 一个请求在代码里的最小落点在 SSM 里Controller、Service、Mapper 三层各占一个包。下面这段是“按学号查学生”的最小代码路径Controller RequestMapping(/student) public class StudentController { Resource private StudentService studentService; RequestMapping(/detail) public String detail(String stuNo, Model model) { Student student studentService.getByStuNo(stuNo); model.addAttribute(student, student); return student/detail; } }Service Transactional public class StudentServiceImpl implements StudentService { Resource private StudentMapper studentMapper; Override public Student getByStuNo(String stuNo) { return studentMapper.selectByStuNo(stuNo); } }Controller 只做两件事拆参数、选视图Service 层声明事务并提供业务方法真正的 SQL 与参数绑定落在 Mapper 接口和它的 XML 里。这样一拆页面、接口、SQL 三层可以并行开发也方便后端单独跑测试。2.4 Service 与 Controller 的边界别越界初次写这类学生管理系统时常见做法是把业务逻辑全堆在 Controller 里Service 层变成空壳甚至 JDBC 代码直接出现在 Controller 中被迫把 DataSource 注入到 web 层。这种代码的问题不是跑不通而是事务失效、SQL 与业务耦合、难以单元测试。反过来也别把 HTTP 参数解析往 Service 里扔Service 层不应该看到 HttpServletRequest 这类 Servlet API。这个分工关系就是写这个系统时不需要文档也能长期维护的关键。3. 先把数据库建对学生管理系统的表设计与 MyBatis 映射3.1 五张核心表与建表 SQL高校学生管理系统的业务数据比较固定学生、教师、课程、选课成绩再配上院系。把它们拆成五张核心表足够覆盖增删改查的主流程。CREATE TABLE department ( dept_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 院系ID, dept_name VARCHAR(50) NOT NULL COMMENT 院系名称 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE student ( stu_id INT PRIMARY KEY AUTO_INCREMENT, stu_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, stu_name VARCHAR(30) NOT NULL, gender CHAR(1) DEFAULT 0 COMMENT 0男 1女, dept_id INT COMMENT 所属院系, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE teacher ( teacher_id INT PRIMARY KEY AUTO_INCREMENT, teacher_no VARCHAR(20) NOT NULL UNIQUE, teacher_name VARCHAR(30) NOT NULL, dept_id INT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE course ( course_id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL UNIQUE COMMENT 课程编号, course_name VARCHAR(50) NOT NULL, credit DECIMAL(3,1) COMMENT 学分, teacher_id INT COMMENT 任课教师 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sc ( id INT PRIMARY KEY AUTO_INCREMENT, stu_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2), UNIQUE KEY uk_stu_course (stu_id, course_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;之所以用 utf8mb4 而不是 utf8是因为 emoji、生僻字这类四字节字符在 utf8 下会报 “Incorrect string value” 错误对管理系统里的姓名、备注字段尤其明显。学号和课程编号这类业务键要加 UNIQUE不能只依赖主键去重。sc 表的唯一键 uk_stu_course 保证同一个学生对同一门课只能有一条成绩记录这是数据库课程设计里经常被追问的约束写法。3.2 多对多关系用 sc 中间表拆开学生和课程是典型的多对多关系一个学生选多门课、一门课被多个学生选所以不会在 student 表里直接加 course_id 字段而是通过 sc 表来建中间关系。teacher 和 course 是一对多course 表里放 teacher_id 即可。至于是否要建物理外键我一般偏向“保留逻辑外键、物理外键可以不加”学生管理系统演示阶段的数据量小物理外键能约束脏数据代价是批量导入和删除时容易踩外键检查能说出这个取舍比单纯建外键在数据库面试题里更有区分度。3.3 StudentMapper.xml 里的三个高频坑Mapper 接口与 XML 里的 SQL 对应时要保证三个一致命名空间等于接口全限定名、方法名等于 SQL 的 id、参数类型和返回类型匹配。mapper namespacecom.example.system.mapper.StudentMapper resultMap idstudentMap typecom.example.system.entity.Student id propertystuId columnstu_id/ result propertystuNo columnstu_no/ result propertystuName columnstu_name/ /resultMap select idselectByStuNo parameterTypestring resultMapstudentMap SELECT stu_id, stu_no, stu_name, gender FROM student WHERE stu_no #{stuNo} /select /mapper参数绑定必须用#{}它会生成预编译的?占位防止 SQL 注入${}是字符串拼接只有动态排序列名或表名时才考虑且绝不能直接拼用户输入。resultMap 里的 property 对应 Java 驼峰属性column 对应数据库下划线列名如果项目开启了mapUnderscoreToCamelCasetrue可以省略大部分 result 映射但主键那一行建议保留。3.4 数据源参数的合理默认值在 dbconfig.properties 里通常这样配jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/student_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password123456 jdbc.initialSize5 jdbc.maxActive20 jdbc.maxWait60000MySQL 8 的驱动类名是 com.mysql.cj.jdbc.DriverMySQL 5 时代是 com.mysql.jdbc.Driver混用会在启动时报 ClassNotFoundException。url 里的 serverTimezone 不加会报时区错误useSSLfalse 是避免本地开发环境没有证书时出现连接告警。连接池参数里 maxActive20 对一个并发不高的管理系统足够了值调得过大反而占用 MySQL 连接资源。4. Spring、SpringMVC、MyBatis 三份配置手动整合的要点和坑4.1 配置文件与目录的约定手动整合 SSM 最容易出事的就是“该有的类都有但配置没配对”。可以先确立下面的目录结构src/main/java com.example.system controller/StudentController.java service/StudentService.java impl/StudentServiceImpl.java mapper/StudentMapper.java entity/Student.java src/main/resources spring/applicationContext.xml spring/spring-mvc.xml dbconfig.properties log4j.properties src/main/webapp/WEB-INF/web.xml这里把 Spring 根容器配置和 SpringMVC web 层配置拆成两份是 SSM 整合的惯例也是 java 面试八股文里常考的“父子容器”概念的落点。4.2 applicationContext.xml数据源、SqlSessionFactory 与事务context:property-placeholder locationclasspath:dbconfig.properties/ bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ property nameinitialSize value${jdbc.initialSize}/ property namemaxActive value${jdbc.maxActive}/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property nametypeAliasesPackage valuecom.example.system.entity/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.example.system.mapper/ /bean bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/这段配置是整套系统的骨架。context:property-placeholder 把 dbconfig.properties 里的连接参数解析成占位符SqlSessionFactoryBean 里两个关键属性typeAliasesPackage 让 XML 里写实体类可以用简单名mapperLocations 指向 XML 文件位置MapperScannerConfigurer 负责把 mapper 接口扫成 Spring Bean事务管理器用 DataSourceTransactionManager再开启注解驱动Transactional 才会实际生效。4.3 spring-mvc.xml扫描边界与静态资源放行context:component-scan base-packagecom.example.system.controller/ mvc:annotation-driven/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean mvc:default-servlet-handler/组件扫描的边界要划清楚spring-mvc.xml 里只扫 controller 包applicationContext.xml 里扫 service、mapper 等业务包两边不能直接全量扫到 com.example.system否则 service 的代理与事务配置会被 web 容器重复加载出现事务不生效或 Bean 冲突。InternalResourceViewResolver 决定 Controller return 出来的视图逻辑名。最后一行 default-servlet-handler 处理的是 B/S 架构里最典型的静态资源被 DispatcherServlet 拦截的问题不加它css、js、图片会全部 404。4.4 web.xml两个容易漏掉的配置web.xml 里的 DispatcherServlet 会让所有路径进入 SpringMVC也因此带来两个高频坑一是中文参数乱码需要过滤器最靠前放置并强制 UTF-8二是 load-on-startup 必须给一个正整数否则 SpringMVC 容器不会随 Tomcat 启动创建导致访问时找不到 DispatcherServlet。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 /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping 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-mappingSpring 根容器通过全局监听器加载listener-class 写 org.springframework.web.context.ContextLoaderListener参数指向 applicationContext.xml。注意 url-pattern 写/会覆盖 Tomcat 默认静态资源 Servlet所以上面必须配 default-servlet-handler如果写成/*连 JSP 渲染都会被拦截项目直接白屏。4.5 手动整合最常报的三个错报错关键字原因处理Invalid bound statement (not found)mapper-locations 没扫到 XML或 Mapper 接口没被扫描检查 SqlSessionFactoryBean 的 mapperLocations 路径和 MapperScannerConfigurer 的 basePackage占位符解析失败dbconfig.properties 没加载或 ${} 键名拼写不一致确认 context:property-placeholder 存在且 location 拼写正确访问接口 404DispatcherServlet 映射覆盖了静态资源或 Controller 没进入扫描包看启动日志中 RequestMapping 是否注册再检查扫描边界启动时看到 “BeanFactory not initialized” 这类容器错误优先怀疑 web.xml 里的 context-param 与 listener 配置而不是先改 Java 代码。5. 打包部署在 Tomcat 上把这个 B/S 系统跑起来5.1 为什么用 WAR 包而不是 JARSSM 项目按 B/S 架构交付时标准形态是把工程打成 WAR 包丢进 Tomcat 的 webapps。JAR 通常对应 Spring Boot 内嵌 Tomcat 的自包含应用而这个工程没有内嵌容器所以打 WAR 更贴近源码部署场景。pom.xml 里需要packagingwar/packaging build finalNamestudent-system/finalName plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-war-plugin/artifactId version3.3.2/version /plugin /plugins /buildfinalName 直接决定部署后的访问路径部署完配合上下文访问地址就是 http://localhost:8080/student-system/。我一般会把 finalName 设置成有意义的项目代号方便多项目在同一个 Tomcat 里共存时区分上下文路径。5.2 打包命令与部署细节mvn clean package -DskipTests这一步会在 target 目录生成 student-system.war。部署时把它复制到 Tomcat 的 webapps 目录下执行 bin/startup.shWindows 是 startup.batTomcat 启动时自动解压 war 包。需要注意解压出的目录名就是 war 包名如果已有同名目录建议先停 Tomcat、删掉旧目录再覆盖避免文件残留导致改了 JSP 不生效。初次部署建议先访问 http://localhost:8080/ 确认 Tomcat 首页正常再访问项目路径。JSP 页面放在 /WEB-INF/views/ 下时浏览器不能直接访问 /WEB-INF/ 下的文件这是 Servlet 规范对受保护资源的限制必须通过 Controller 跳转。5.3 部署后最常见的三类问题现象定位方向处理页面有 HTML 但 css/js 全挂DispatcherServlet 拦截了静态资源确认 spring-mvc.xml 已加 default-servlet-handlerJSP 里的资源路径带 pageContext.request.contextPath直接访问接口地址 404请求没匹配到 Controller看启动日志里是否打印 RequestMappingHandlerMapping 与映射地址检查 Controller 是否被扫到页面 500 且一堆 javax.* 异常依赖缺失或版本冲突用 mvn dependency:tree 查重复依赖重点看 jackson、spring-aop 相关包是否被多版本引入B/S 架构下客户端只留浏览器所有问题都靠服务器端日志定位这也是为什么保证日志正常输出比调一个好看的页面更重要。6. 跑通后用 JUnit、DEBUG 日志和几个自检技巧收尾6.1 Service 层跑一个 JUnit 测试RunWith(SpringJUnit4ClassRunner.class) ContextConfiguration(locations classpath:spring/applicationContext.xml) public class StudentServiceTest { Resource private StudentService studentService; Test public void testGetByStuNo() { Student student studentService.getByStuNo(20240001); Assert.assertNotNull(按学号查询不应返回null, student); } }这个测试直接走 Spring 容器拿 service绕过了 Controller 和网络层能最快定位问题在业务逻辑还是在配置。6.2 打开 MyBatis 的 DEBUG 日志在 log4j.properties 里追加两行log4j.logger.org.mybatisDEBUG log4j.logger.java.sqlDEBUG控制台会打印出每条实际执行的 SQL 及参数值例如 Preparing: SELECT ... WHERE stu_no ?。遇到查询结果和预期不符时先看这条日志确认 SQL 是否与你手工执行的版本一致参数是不是被传成了空串。6.3 最后再查一遍乱码、事务、Tomcat 缓存三个高频收尾项中文乱码检查 url 是否带 characterEncodingutf8 以及过滤器顺序事务不生效检查有没有配置 tx:annotation-driven以及 Transactional 是否被同类内部方法调用JSP 改完不生效清掉 Tomcat work 目录下对应项目的缓存再重启。另外注意几个入口层面的自检顺序——访问某个页面 404 先看 Tomcat 访问日志确认请求进没进容器进了容器的再翻 SpringMVC 映射日志没进的去看部署目录是否解压完整。按照这个顺序检查SSM 项目里九成“看着像代码问题”的情况都能在十分钟内定位到源头。本文还有配套的精品资源点击获取