Spring+SpringMVC学生信息管理系统:搭建、配置与核心原理全解析

发布时间:2026/9/9 0:02:12
Spring+SpringMVC学生信息管理系统:搭建、配置与核心原理全解析 简介基于SSM框架SpringSpringMVCMyBatis的学生信息管理系统面向Java Web学习者和毕业设计、课程设计人群。资源共2个文件包含ZIP源码包和SQL数据库脚本脚本可直接初始化学生、课程等核心表源码内含Controller、Service、DAO分层代码整体大小16.32MB。已有2391人学习下载。系统实现学生信息增删改查、登录认证与权限控制等典型功能IDE导入即可运行。通过阅读工程可掌握Spring依赖注入、SpringMVC请求处理、MyBatis动态SQL及事务管理思路并借鉴其表结构与接口设计是管理类课程项目或毕设的有用参考。 学生信息管理系统几乎是每个学Java Web的人都会碰到的项目。前几天还有位读者私信我课程设计指定要用 Spring SpringMVC他网上一搜要么是Spring Boot的自动生成模板要么是十几年前的老SSH照着敲还反复报错。这篇文章就拿“学生信息管理系统”当例子把Spring SpringMVC从工程搭建、双容器配置、功能实现到常见报错和核心原理全部过一遍。项目本身不算复杂但Spring的IOC依赖注入、事务管理、SpringMVC的请求路由和拦截器都能在这套系统里找到最典型的使用场景。如果你正在做课程设计或者刚学完Servlet想向框架分层过渡这篇应该能帮你省不少时间。1. 项目定位为什么学生信息管理系统适合用来学框架1.1 功能需求与数据模型设计学生信息管理系统本质上就是一套标准的CRUD项目对学生基本信息做增删改查。但正因为业务足够简单它不会像复杂商城项目那样把框架原理淹没在海量业务逻辑里反而更适合观察框架的工作方式。我建议课程设计版本至少包含这几个功能点登录页账号密码校验后写入Session学生信息新增、修改、删除按学号、姓名或班级做模糊查询列表分页展示表单的非空校验和学号唯一性校验。数据表也不用设计得太复杂一张student表就够字段类型说明idint主键自增student_novarchar(20)学号建议加唯一索引namevarchar(50)姓名genderchar(1)性别ageint年龄class_namevarchar(50)班级phonevarchar(20)联系方式create_timedatetime创建时间很多同学一上来先把表设计成五六个关联表然后卡在关联查询里出不来。课程设计的核心不是炫技而是把Spring和SpringMVC的主链路跑通单表反而能看到最清晰的调用链。1.2 为什么选 Spring SpringMVC而不是 Spring Boot这是很多人忽视的问题。Spring Boot确实能一键启动项目但自动配置把web.xml、DispatcherServlet、容器初始化这些关键过程全部隐藏了。如果连Spring容器和SpringMVC容器之间的关系都没搞清楚直接上手Spring Boot遇到问题只能靠猜。Spring和SpringMVC在这个项目里的分工很明确Spring 管理Service层、DAO层这些对象的创建和依赖关系同时负责事务和AOPSpringMVC 负责Web层处理URL映射、参数绑定、请求转发和视图解析。我常打一个比方Spring是后勤大管家负责把各种零件组装好SpringMVC是前台接待所有请求先到它手里再由它找对应的Controller处理。Servlet时代你连这些对象的生命周期都要自己管现在框架帮你管好了但前提是你得知道有人在做这件事。用这组框架还有一个好处等以后你切换到Spring Boot会发现Spring Boot只是帮Spring和SpringMVC做了自动配置底层的IOC容器和请求分发机制没有变。先学这套手工配置版本再去看Boot的自动装配思路会非常顺。2. 工程骨架搭建web.xml、配置文件与项目结构2.1 项目目录与依赖引入工程不是Maven项目的话我会建议立刻转成Maven。手工拷jar包不是不行但版本冲突处理起来特别花时间。包名我习惯用com.example.student按层分包com.example.student ├── controller # SpringMVC控制器 ├── service # 业务接口 │ └── impl # 业务实现 ├── dao # 数据访问层 ├── entity # 实体类 ├── interceptor # 拦截器 └── util # 分页等工具类pom.xml里最核心的几个依赖如下properties spring.version5.3.39/spring.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.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version scoperuntime/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjstl/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependencies加粗提示一下javax.servlet-api一定要用provided作用域。因为Tomcat自带Servlet容器如果打成war包时把这个依赖打进去会和容器里的类冲突项目直接启动失败。2.2 双容器配置Spring 与 SpringMVC 各管什么Spring和SpringMVC配合时会创建两个IOC容器这也是初学者最容易懵的地方。父容器由ContextLoaderListener加载扫描Service、DAO子容器由DispatcherServlet加载只扫描Controller。子容器可以拿到父容器的Bean反过来不行。先看父容器applicationContext.xmlcontext:component-scan base-packagecom.example.student context:exclude-filter typeannotation expressionorg.springframework.stereotype.Controller/ /context:component-scan context:property-placeholder locationclasspath:jdbc.properties/ bean iddataSource classorg.springframework.jdbc.datasource.DriverManagerDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /bean bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/上面的组件扫描排除了Controller确保Service和DAO归父容器管。事务管理器基于数据源创建Transactional注解才会生效。再看子容器springmvc.xmlcontext:component-scan base-packagecom.example.student context:include-filter typeannotation expressionorg.springframework.stereotype.Controller/ context:exclude-filter typeannotation expressionorg.springframework.stereotype.Service/ /context:component-scan mvc:annotation-driven/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean mvc:resources mapping/static/** location/static//mvc:annotation-driven这一行不能少它注册了HandlerMapping、HandlerAdapter这些基础组件。视图解析器把Controller返回的逻辑视图名拼成/WEB-INF/views/xxx.jsp的实际路径。mvc:resources则是给静态资源放行否则CSS、JS会被DispatcherServlet拦下来。2.3 请求从浏览器到Controller的完整链路web.xml里注册DispatcherServlet是整个Web应用的入口servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:springmvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mappingurl-pattern用/还是*.do我建议直接用/。用*.do的话接口路径上还得额外加后缀老项目喜欢这么干但不属于REST风格。用/会接管所有请求所以必须搭配mvc:resources处理静态资源。一个请求完整走一遍是这个流程浏览器发起/student/list请求请求先到 Tomcat因为url-pattern匹配被DispatcherServlet接收DispatcherServlet通过HandlerMapping找到处理该路径的Controller方法HandlerAdapter反射调用Controller方法方法里调用ServiceService调用DAOController返回逻辑视图名和Model数据视图解析器解析出JSP路径渲染后响应给浏览器。理解这条链路后面排查404和500就有方向了请求没进Controller是前几步的问题请求进了Controller但页面不对是后几步的问题。3. 核心功能落地CRUD、分页搜索和登录拦截3.1 增删改查与参数绑定细节Controller层的写法在项目里差异不大我重点说几个容易出错的点。新增学生信息前端表单提交到/student/saveController用实体类直接接收默认参数绑定前提是表单控件的name属性必须和Student字段名一致Controller RequestMapping(/student) public class StudentController { Autowired private StudentService studentService; PostMapping(/save) public String save(Student student) { studentService.insert(student); return redirect:/student/list; } }这里有一个参数绑定的大坑Student里如果有日期类型字段比如birthday表单提交的字符串1998-05-12默认无法直接转成Date。解决方案是在日期字段上加格式化注解public class Student { private Integer id; private String studentNo; DateTimeFormat(pattern yyyy-MM-dd) private Date birthday; // 其他字段和getter/setter省略 }别忘了在springmvc.xml里已经配置了mvc:annotation-drivenDateTimeFormat才起作用。忘了加注解最常见的结果是表单提交直接报400。修改操作要注意“先回显再提交”。Controller通过id查出学生丢到Model里JSP回填表单GetMapping(/edit) public String edit(RequestParam(id) Integer id, Model model) { model.addAttribute(student, studentService.getById(id)); return student/edit; }这里如果使用PathVariable接收/student/edit/{id}这种路径参数要注意前端拼接路径的正确性。我用RequestParam更多一些因为课程设计阶段常见的操作是student/edit?id1这种查询字符串传参出错率更低。删除操作走POST提交更稳妥但很多人图方便直接写超链接触发删除。我可以理解课程设计要节省时间但这里还是建议至少做一次确认弹窗别点一下就删一条数据。3.2 分页与模糊查询的SQL封装分页是课程设计答辩最容易追问的功能点。手写一个Page工具类就够了public class PageT { private Integer currentPage 1; // 当前页 private Integer pageSize 5; // 每页条数 private Integer totalCount; // 总条数 private Integer totalPage; // 总页数 private ListT data; // 当前页数据 public Integer getStartRow() { return (currentPage - 1) * pageSize; } }Service层先查总条数再查当前页数据Override public PageStudent queryPage(Integer currentPage, Integer pageSize, String keyword) { PageStudent page new Page(); page.setCurrentPage(currentPage); page.setPageSize(pageSize); Integer totalCount studentDao.count(keyword); page.setTotalCount(totalCount); int totalPage (totalCount pageSize - 1) / pageSize; page.setTotalPage(totalPage); page.setData(studentDao.selectPage(page.getStartRow(), pageSize, keyword)); return page; }DAO层用Spring JDBC Template实现注意查询时必须用占位符不能字符串拼接SQLpublic ListStudent selectPage(int startRow, int pageSize, String keyword) { String sql SELECT * FROM student WHERE name LIKE CONCAT(%, ?, %) OR student_no LIKE CONCAT(%, ?, %) ORDER BY id DESC LIMIT ?, ?; return jdbcTemplate.query(sql, new BeanPropertyRowMapper(Student.class), keyword, keyword, startRow, pageSize); }这里CONCAT(%, ?, %)是故意这么写的不要直接写%${keyword}%那会被SQL注入。下一屏再算总数的时候条件保持一致即可保证列表页和分页条的总数对得上。3.3 用 SpringMVC 拦截器完成登录态校验学生信息管理系统的登录功能用Filter能做用SpringMVC的拦截器做更符合框架习惯而且 HandlerInterceptor 的核心扩展点有三个preHandle在进入Controller方法前执行可以做登录校验、权限控制postHandle在Controller方法执行后、视图渲染前执行可以往Model里补公共数据afterCompletion整个请求完成后执行适合做日志和资源清理。登录校验写在preHandle里就够public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser request.getSession().getAttribute(loginUser); if (loginUser null) { response.sendRedirect(request.getContextPath() /login); return false; } return true; } }在springmvc.xml里注册这个拦截器同时放行登录相关的路径mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/static/**/ bean classcom.example.student.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptors这里最容易被忽略的是exclude-mapping。如果忘记排除login路径就会出现一个很尴尬的死循环访问首页被拦重定向到登录页登录页又被拦又重定向回登录页。那为什么不直接Filter呢因为HandlerInterceptor最大的优势是第三个参数handler可以拿到当前要执行的Controller方法信息比如配合自定义注解做更细粒度的权限控制。Filter在Web层更靠前但对SpringMVC内部的处理过程一无所知。4. 高发问题与Spring核心机制排查4.1 报错速查表我自己带大家做项目时整理过一个高频报错表照着查比瞎改快得多报错现象根因解决方案启动后访问路径404控制台无报错DispatcherServlet的url-pattern没匹配到或Controller没被扫描检查web.xml的servlet-mapping检查springmvc.xml的扫描包路径页面能渲染但样式全丢静态资源被DispatcherServlet拦截配置mvc:resources或用default-servlet-handler放行表单提交400参数名和实体类字段不一致或日期格式无法转换补齐表单name日期字段加DateTimeFormat数据库查询报Unknown columnSQL里写了Java属性名下划线风格SQL中使用数据库字段原名如student_no中文乱码缺少编码过滤器在web.xml配置CharacterEncodingFilter强制UTF-8事务不生效Transactional写在接口方法上或没配transactionManager写实现类public方法上配好DataSourceTransactionManagerBean named xxx is expected...接口和实现的包扫描路径不一致统一包名或手动注册Bean对比看下来会发现大部分问题集中在包扫描、配置路径、依赖作用域这三处全是配置细节。我建议搭建项目时不要直接跑先用控制台日志确认Spring容器和SpringMVC容器初始化成功再写业务代码出问题能大幅缩小排查范围。4.2 循环依赖与Spring三级缓存原理学Spring绕不开循环依赖这也是面试必问的点因为学生信息管理系统里很容易出现一个真实场景写学生模块的同时写了班级模块然后StudentServiceImpl注入了ClassServiceClassServiceImpl又注入了StudentService互相引用。启动时Spring创建Bean自然会形成一个循环。先看报错长什么样通常是类似这句话org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name studentService: Requested bean is currently in creation: Is there an unresolvable circular reference?Spring解决单例Bean循环依赖依赖的是三级缓存这三层是这样的第一级singletonObjects存已经完成初始化的完整Bean第二级earlySingletonObjects存提前暴露的、还没完成属性填充的原始Bean第三级singletonFactories存ObjectFactory对象工厂这个工厂在需要时决定返回普通对象还是AOP代理对象。创建流程简化理解创建A时先实例化出A的原始对象然后把这个原始对象的ObjectFactory放进三级缓存再去填充A的属性。此时发现依赖B于是去创建BB又依赖AB顺着三级缓存找到A的ObjectFactory提前拿到A的引用B正常创建完成A再从缓存里拿到B完成属性填充最终把完整对象放到一级缓存。那为什么二级缓存不够一定要三级关键在于AOP代理。如果属性填充时就必须决定A是普通对象还是代理对象此时A的切面信息可能还没准备好。三级缓存里的ObjectFactory把这个决策延迟到了真正需要引用A的那一刻由工厂决定返回原始对象还是代理对象。这是Spring设计里非常精妙的一笔。日常写代码时我更推荐避免循环依赖构造器注入的好处是强制你按依赖顺序创建对象无法循环依赖SpringBoot高版本也在提倡构造器注入。如果在项目重构里真遇到改不动的循环依赖可以用Lazy注解延迟其中一个Bean的初始化时间这也是常见解法。这个机制在学生信息管理系统里用不上但课程设计答辩被问到Spring能答这块基本就是高分项。5. 从课程设计到进阶这套项目还能怎么演化5.1 迁移到Spring Boot与MyBatis做完Spring SpringMVC版本后你可以试着把它迁移到Spring Boot。对比一下就能理解“约定大于配置”的含义web.xml和两个XML配置文件会被自动配置取代目录结构更固化启动类位于根包控制器、业务层、数据层放在启动类同级或子包下就行不用手动做包扫描配置。再配上MyBatis动态SQL处理那些“查询条件未必都有值”的场景会很舒服比如学生列表页的关键词搜索条件用where标签就能自动拼条件比手动拼接SQL更整洁。Spring Boot提供spring-boot-starter-jdbc和spring-boot-starter-web基本就是当初Spring SpringMVC这套东西的打包版本。如果你对权限部分有兴趣后续可以换Spring Security替代手写拦截器。但要说清楚Spring Security的过滤器链机制更底层也更强大做学生管理系统手写拦截器完全够用不用为了技术而技术。5.2 手写一个mini版Spring这句话我说过很多次想真正理解Spring IOC最快的路径是“手写Spring”。写个200行以内的迷你版IOC容器核心就三步扫描指定包下的所有class文件过滤带Component等注解的类用反射创建对象实例遍历对象的字段发现带Autowired注解的字段就完成依赖注入。调通之后你会突然明白Autowired的默认行为是按类型注入还是按名称注入也会明白为什么接口要设计成单一职责。再手写一个迷你DispatcherServlet模拟URL和Controller方法的映射过程SpringMVC请求链路也就刻在脑子里了。这个思路尤其适合那些秋招想冲Java岗的同学。只看《Spring源码深度解析》啃不下去就先动手做迷你版比死记硬背效率高得多。5.3 给课程设计加分的几个方向功能做完以后如果还想往上拔一拔我建议优先加这四样学号唯一性校验用AJAX异步判断注册时前端实时提示导出学生名单Excel用POI或EasyExcel实现考核老师喜欢这个AOP统一日志记录每个接口的访问参数和耗时全局异常处理用ControllerAdvice把业务异常和系统异常区分开来返回统一提示页。这些模块单独拆出来都不难但放在一个系统里能体现你在分层设计上的思考。最怕的是把代码全堆在Controller里比如直接在Controller里写JDBC查询那Spring的IOC和SpringMVC的分层就完全没发挥出来答辩很难自圆其说。最后分享一点我做课程设计指导的经验很多人能在运行阶段调试出功能但被追问“Spring容器什么时候创建、Controller和Service为什么不在同一个容器、拦截器为什么拦不住静态资源”就答不上来。问题不在于你不懂某个具体API而是整体请求链路没有打通。Spring SpringMVC这个组合虽然配置繁琐但它把所有关键环节都摊在你眼前亲手跑通一遍之后后面学Spring Boot的自动装配、学Spring Security的过滤器链、学Spring AI这类新生态都会顺畅很多。别图快直接复制别人工程自己搭一遍收获远超预期。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询