ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SSM学生信息管理系统开发全流程:从需求拆分到部署排错

SSM学生信息管理系统开发全流程:从需求拆分到部署排错 我做这个基于SSM的学生信息管理系统之前已经在别的项目里零零散散写过Spring MVC和MyBatis的代码但真正从零把一个带登录、分页、权限、导入导出功能的完整系统跑起来还是在做这个项目的时候。本文会完整还原我从需求拆分、数据库设计、SSM整合、Mapper编写、Service事务、前端联调到最终部署排错的全过程。如果你正在做JavaWeb课程设计或者刚接触SSM这套组合想找一个能真正跑通、能反复修改的项目源码作为参考这篇内容应该可以直接帮你少走很多弯路。我会把很多当时没想明白的点也一起写出来。很多事情不是一开始就知道的比如MyBatis动态SQL里where标签为什么会自动处理第一个AND比如前端表格的日期格式化为什么后端返回的是乱糟糟的数字再比如拦截器碰上Ajax请求时该怎么返回。咱们一个个说。1. 学生信息管理系统到底在管什么1.1 先做需求拆分再谈技术选型很多同学一拿到“学生信息管理系统”这个题目第一反应就是打开IDE创建Maven工程马上开始配web.xml、DispatcherServlet、数据源和MyBatis。这种做法不是不行但很容易陷入“功能都做完了答辩时却讲不清楚”的尴尬。因为需求如果不先拆清楚你根本不知道自己写出来的东西到底要解决什么问题。我当时把需求拆成了六大块登录与注销、学生档案管理、班级管理、筛选与分页、Excel导入导出、角色权限控制。学生档案管理看起来就是增删改查但真实业务里还藏着几个问题学号能不能重复删除学生时相关的班级数据要不要联动处理修改学生班级时需不需要记录操作日志分页查询时筛选条件如何保持住这些问题如果不提前想好后面的代码会越写越乱。更关键的是角色权限。这个系统如果没有登录功能那它只能算一个“表单示例”不能叫“管理系统”。教务老师登录后可以录入学生、修改班级普通老师可能只需要查看列表。如果不区分角色直接让所有人看到同一个页面业务上就说不过去。建议你在动手写任何代码之前先拿出一张纸把每个角色的权限和每个按钮的行为写清楚不要急着写代码。1.2 为什么是这个年代了还选SSM现在Spring Boot确实很主流新项目几乎清一色用的是Spring Boot。但SSM这三个框架的组合到今天依然是理解JavaWeb运行机制的最好教材之一。原因很简单。DispatcherServlet是怎么拦截请求的HandlerMapping怎么找到对应的方法SqlSessionFactory里的Mapper代理对象是怎么生成出来的这些在SSM里是一条完整链路暴露给开发者的。如果你在Spring Boot里干活一个SpringBootApplication注解加自动配置把很多细节都封装掉了出了问题反而不知道该从哪里查起。再有就是企业存量。很多公司内部系统依然是Spring MVC MyBatis的老骨架维护这些系统的工程师必须能够看懂SSM的配置和代码。会Spring Boot不一定会SSM反之会SSM再切Spring Boot会容易得多。所以从学习角度来说SSM并不多余。不过我也要承认在配置量上SSM确实繁琐一点。spring-context.xml、spring-mvc.xml、mybatis-config.xml、jdbc.properties每个文件都得手动维护。我的做法是把注释写得很详细后期改动定位速度快很多。2. 技术选型与项目初始化2.1 框架版本与核心依赖SSM不是一个具体的框架而是一组框架的组合。真正落地时还要考虑JDK、Tomcat、MySQL、Maven、Jackson、JSTL这些配套组件。版本选择上不用追新关键看兼容性。我这里给出一个经过验证的稳定组合JDK 1.8Spring 5.2.xSpring MVC 5.2.xMyBatis 3.5.xMyBatis-Spring 2.0.xMySQL 8.0.xTomcat 9.0Maven 3.6我见过有人用Spring 4.3配MyBatis 3.5启动的时候直接报NoSuchMethodError最后定位出来是两套框架整合版本冲突。最好的办法就是别自己创造组合直接参照官方文档推荐的兼容版本或者找一个经过验证的模板项目来改。下面是精简后的pom.xml核心依赖片段去掉了打包插件部分properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding spring.version5.2.22.RELEASE/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 groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.11/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.30/version scoperuntime/scope /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.13.4/version /dependency /dependencies这里有个需要提醒的坑如果你使用JSP作为视图层并且用到了JSTL标签库记得在依赖里加上jstl和taglibs否则页面上会出现${student.name}原样输出或者数据怎么都显示不出来的怪问题。我刚开始就因为漏了JSTL页面上一堆空值排查了快一小时才发现是依赖缺失。2.2 包设计与Spring配置结构一个清晰的包结构能让人不用看代码就能知道系统有哪些职责。我的项目结构大致是这样的src/main/java com.example.studentms ├─ controller # 控制器 ├─ service # 业务接口 │ └─ impl # 业务实现 ├─ mapper # MyBatis Mapper接口 ├─ entity # 实体类 ├─ dto # 参数对象与分页返回 ├─ common # 统一返回结果、异常类 └─ interceptor # 登录拦截器 src/main/resources ├─ spring/spring-context.xml ├─ spring/spring-mvc.xml ├─ mapper/StudentMapper.xml ├─ mybatis-config.xml └─ jdbc.propertiesController层只做三件事接收参数、调用Service、封装返回值。真正的业务规则比如学号唯一性、删除班级前检查学生数量、账户被禁用时不能登录这些都放在Service层。不少新手会把业务逻辑直接写在Controller里前期看起来代码量少但一个方法里要是混着请求校验、逻辑判断、数据库查询、返回值拼接后期维护成本会非常高。Spring配置文件我拆成了两份。spring-context.xml管数据源、事务、Mapper扫描这些非Web的部分spring-mvc.xml管视图解析器、静态资源映射、注解驱动这些Web部分。之所以分开是为了以后写单元测试时不需要启动整个Web容器直接加载spring-context.xml就够了。2.3 源码包内容与推荐的阅读顺序如果你拿到的是完整源码包建议不要上来就打开Controller类乱翻。正确顺序是先看数据库脚本再看配置文件然后看Mapper的XML最后才看Service和Controller。因为数据库脚本决定数据模型配置决定框架怎么组装Mapper XML才是真正执行SQL的地方。很多同学对着StudentMapper.java接口找不到SQL执行逻辑就是因为没有去看同名XML文件里的namespace和SQL语句。源码包里除了Java代码和前端页面还应该包含README和一个初始化数据库的SQL脚本。README里至少写清楚JDK版本、MySQL版本、默认管理员账号密码、部署步骤不然换个环境可能跑不起来。3. 数据库设计与Mapper层实现3.1 学生、班级、用户三张核心表数据库设计是整个系统最容易返工的地方。我第一版设计时图省事把班级名称、班主任、年级都塞进了学生表结果班级一改名历史数据全部要跟着改。最终我采用了经典的三表设计学生表、班级表、用户表。学生表student字段名类型说明idbigint主键自增student_novarchar(20)学号唯一索引namevarchar(50)姓名gendertinyint性别1男2女birth_datedate出生日期phonevarchar(20)联系电话emailvarchar(100)邮箱class_idbigint班级外键statustinyint状态1在读0离校create_timedatetime创建时间update_timedatetime更新时间班级表class_info字段名类型说明idbigint班级IDclass_namevarchar(100)班级名称gradevarchar(20)年级majorvarchar(50)专业head_teachervarchar(50)班主任create_timedatetime创建时间用户表sys_user字段名类型说明idbigint用户IDusernamevarchar(50)登录名唯一passwordvarchar(100)密码存储MD5或BCrypt值real_namevarchar(50)真实姓名roletinyint角色1管理员2普通教师statustinyint状态1启用0禁用这里特别提醒一下不要用user做表名。user在很多数据库版本里是保留字写SQL时要么加反引号要么换成sys_user。同样值得避开的还有order、group、desc这些词。我后来统一在命名时避开保留字省得换数据库版本时踩坑。关于外键我的建议是逻辑外键而不是物理外键。学生表通过class_id关联班级这个关系在JOIN查询时体现即可。物理外键会在插入、删除时增加额外的约束校验数据量大了以后迁移、分表都非常麻烦。实际开发中很多项目都不建物理外键效果没什么区别代码却灵活得多。3.2 MyBatis动态SQL与多条件查询学生列表最常见的需求是按条件筛选按姓名模糊查询、按班级筛选、按性别筛选、按状态筛选再结合分页。如果每个条件都写一个方法代码会非常冗余。MyBatis的动态SQL标签正好解决这个问题。看一下查询学生分页列表的核心XMLselect idselectStudentPage resultTypecom.example.studentms.entity.Student SELECT s.id, s.student_no, s.name, s.gender, s.birth_date, s.phone, s.email, s.status, c.class_name FROM student s LEFT JOIN class_info c ON s.class_id c.id where if testname ! null and name ! AND s.name LIKE CONCAT(%, #{name}, %) /if if testclassId ! null AND s.class_id #{classId} /if if testgender ! null AND s.gender #{gender} /if /where ORDER BY s.create_time DESC LIMIT #{offset}, #{pageSize} /selectwhere标签会自动去掉第一个条件前的AND省去手动拼接的麻烦。如果你用trim标签也可以但where可读性更好。和这个查询配套的还有一个总数查询分页组件需要总记录数才能正确显示页码。如果总数只返回当前页的行数页面上会看到“共3条”这样的假数据。我封装了一个PageResultT对象包含list、total、pageNum、pageSize四个字段Service里先查总数再查列表Controller拿到结果后直接返回给前端。3.3 关联查询与一个讨巧的字段设计学生列表里要显示班级名称而学生表里只存了class_id所以必须关联查询。这里我用的是LEFT JOIN而不是INNER JOIN目的是避免一部分还没分配班级的学生被过滤掉。关联查询返回的class_name并不存在于实体类对应的表字段中但MyBatis允许你返回额外的列并自动映射到Java属性。实体类里多加一个className字段就行数据库不需要真的存在这个字段也不用额外加注解。这个讨巧的设计在多数业务系统中非常常见解决了“页面要展示、但对数据库无关”的字段需求。4. Service层业务逻辑与Controller实现4.1 登录认证与拦截器设计登录是管理系统的标配。我的登录流程简化成三步根据用户名查询用户、判断状态是否正常、比对密码。密码不要明文存数据库哪怕是课程设计级别也至少用MD5配合盐值处理。如果给企业用更推荐BCrypt。Controller里处理登录时我习惯返回统一格式的Map对象PostMapping(/login) ResponseBody public MapString, Object login(String username, String password) { MapString, Object result new HashMap(); User user userService.login(username, password); if (user null) { result.put(code, 500); result.put(msg, 用户名或密码错误); return result; } session.setAttribute(loginUser, user); result.put(code, 200); result.put(msg, 登录成功); return result; }光有登录还不够还得防止用户手动输入/student/list绕过登录页。拦截器是SSM里最简单的权限控制手段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) { String requestedWith request.getHeader(X-Requested-With); if (XMLHttpRequest.equals(requestedWith)) { response.setStatus(401); } else { response.sendRedirect(request.getContextPath() /login); } return false; } return true; } }这里有个很细的点普通页面请求被拦下时可以直接重定向到登录页但Ajax请求如果收到重定向前端拿到的是整个HTML页面而不是JSON数据页面表现会很奇怪。所以拦截器里要判断X-Requested-With请求头给Ajax返回401状态码再由前端统一做跳转处理。这个习惯在前后端分离的项目里尤其重要。4.2 学生信息增删改查与事务管理学生新增和修改的核心逻辑其实很像都需要做唯一性校验。新增时判断学号是否已存在修改时判断“排除当前记录后学号是否已存在”否则编辑一条学生记录时只要没有改动学号也会提示冲突。Service方法上加Transactional能保证中间步骤失败时数据一致。比如新增学生的同时要写一条操作日志日志插入失败时学生也不应该被写入数据库。这里有个容易被忽略的细节Spring声明式事务默认只对运行时异常回滚如果你在Service里自己捕获了异常并打印事务不会回滚。所以我的习惯是Service层不捕获异常让异常抛到上层统一处理。删除班级同样要注意。如果班级下有学生直接删班级就会产生一堆“无班级”学生。我在删除前先统计关联学生数大于0就直接弹提示“该班级下还有学生无法删除”。这个逻辑不复杂但能有效保护数据完整性。4.3 分页与筛选接口的设计分页可以用PageHelper插件但如果你想理解原理手写LIMIT分页也是个好办法。Service层接收pageNum和pageSize计算偏移量offset (pageNum - 1) * pageSize然后传给Mapper。pageSize要做上限限制比如不超过100防止前端传一个5000把数据库查崩。导出和分页查询共用筛选条件只是导出时不带LIMIT。我这里把筛选条件封装成StudentQuery对象Mapper里一个方法走分页列表另一个方法走导出全部结果。这样既避免了重复SQL后面做统计报表也能复用。接口返回值我统一采用{code: 200, msg: , data: ...}的结构。不要每个接口都返回不同类型的数据前端用起来会非常痛苦。统一返回对象以后哪怕接口逻辑改动前端解析代码几乎不用变。5. 前端页面、交互与数据回显5.1 用成熟组件还是手写原生学生信息管理系统的页面用原生表格和表单也能写但很多细节会让你写到怀疑人生比如分页条、日期选择器、按钮弹窗、校验提示。我使用时用了Layui的表格和弹层组件表格异步加载、分页、表单提交这些能力开箱即用适合快速交付。如果不喜欢Layui用Bootstrap配jQuery也可以只是工作量大一些。页面结构大致是这样顶部导航展示当前登录用户和退出按钮左侧菜单是学生管理、班级管理、系统管理内容区域默认进入学生列表页。列表页顶部放筛选表单中间是数据表格底部是分页条。表格初始化时接口路径指向/student/page返回的数据格式要和表格组件约定一致。Layui表格会把count字段当作总数把data字段当作记录数组。两边的格式对不上页面看起来就是“无数据”。5.2 表单回显与日期处理的常见坑编辑学生信息时日期回显不对、下拉框选不中是高频问题。日期问题的根源通常是实体类里用了java.util.DateJSON序列化时默认输出时间戳或者带毫秒的复杂格式。解决办法是在日期字段上加JsonFormat(pattern yyyy-MM-dd)保证前端拿到的就是“2026-01-15”。下拉框选不中一般有这两种原因第一种是弹窗打开时班级下拉数据还没加载完成就急着赋值第二种是下拉选项value的类型和后端返回类型不一致。比如后端classId是Long前端选项value是字符串两者比较时对不上。遇到这类问题打开浏览器开发者工具在控制台里输出一下赋值前后的参数类型很快就能定位。还有一个小细节筛选条件选了班级A翻到第3页后点下一页班级条件不应该丢失。因此查询条件要放到刷新的逻辑里每次翻页时把当前筛选条件一起传给表格而不是只传页码。这种“漏条件分页”的Bug比较隐蔽排查时要格外认真。5.3 前后端双重校验表单校验不能只在前端做。前端校验只是提升体验真正可信的校验必须放在后端。学号长度、电话格式、邮箱格式这些规则在Controller接收参数之后进入Service之前先统一校验一遍。校验失败时抛出业务异常由全局异常处理器统一返回错误信息。我见过一个项目后端没有校验用户在前端绕过限制插入了一条带script标签的文本页面渲染时直接出现了奇怪效果。这说明后端输入校验不仅是业务问题也是安全问题。无论前端写了多少条校验规则后端都必须有自己的完整校验逻辑。6. 编译部署与常见问题排查6.1 从零开始跑通一个SSM项目拿到源码后第一步不是马上点运行而是把环境变量理顺。按下面的顺序操作先创建数据库导入项目附带的SQL脚本确认student、class_info、sys_user三张表都建好然后修改jdbc.properties里的数据库地址、账号、密码检查连接串参数接着在项目根目录执行mvn clean package等待打包成功再把war包部署到Tomcat的webapps目录或者在IDE中配置Tomcat直接启动最后访问http://localhost:8080/studentms/login用管理员账号登录。这里最容易忽略的是MySQL时区问题。MySQL 8的默认时区可能和本地不一致连接串里如果没有serverTimezone启动时会报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized这种乱码错误。连接串里直接加上这段参数就能解决jdbc:mysql://localhost:3306/student_ms?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai6.2 常见报错与修复速查表把实际运行中最常遇到的问题整理成一个速查表方便对号排查。现象可能原因解决方案启动后全部404项目没部署成功或者web.xml配置错误看Tomcat启动日志确认war已展开表单提交中文乱码缺少编码过滤器在web.xml配置CharacterEncodingFilter统一UTF-8CSS、JS加载不出来Spring MVC拦截了静态资源配置mvc:resources映射让静态资源直接放行Mapper接口报BindingException接口没被扫描到或XML路径不对检查basePackage扫描路径和mapper-locations数据库连接失败驱动版本与MySQL不匹配MySQL 8必须使用mysql-connector-java 8.x登录后session频繁丢失session读取和写入名称不一致检查登录时设置的attribute名和Interceptor读取名是否一致排查SSM项目时打印日志比页面弹窗有用一百倍。我习惯在Service层方法入口打印参数在异常处理器里打印完整堆栈。遇到问题不要一直盯着浏览器Tomcat控制台或日志文件里的异常栈能直接告诉你哪一行代码出了问题。6.3 Excel导出与批量导入的实现细节如果功能里需要导出Excel推荐用Apache POI。核心流程是先查出符合条件的全部学生用XSSFWorkbook创建表格逐行写入单元格最后通过HttpServletResponse输出。导出文件名要注意中文编码response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment;filename URLEncoder.encode(学生信息.xlsx, UTF-8));批量导入则相反需要处理上传文件读取每一行逐条校验逐条插入。大数据量导入时建议分批提交比如每500条提交一次。最后在返回结果里统计“成功导入多少条、失败多少条”而不是中间一遇到错误就全部回滚。学号字段要在数据库上建立唯一索引用数据库层面的约束兜底这样即使代码里漏掉去重逻辑重复学号也进不去。最后想说的话这套SSM学生信息管理系统虽然只是一个课程项目级别的系统但完整跑通后我对JavaWeb的理解明显上了一个台阶。透过SSM让我看清了Spring容器、MyBatis代理、Tomcat请求生命周期这些底层的协作关系。如果你也是第一次接触SSM我建议你在运行阶段多打断点看看请求经过了哪些类不要只满足于页面能点。尤其是DispatcherServlet到Controller再到Service再到Mapper这条主链路花一点时间打断点收获比抄代码大得多。源码写完后一定要补一份README把数据库初始化步骤、默认账号密码、部署命令都写清楚。源码包版本号84419里附带的环境配置和部署文档按顺序操作基本十分钟内能跑起来。希望这篇记录能帮你少踩几个坑也建议你在跑通之后试着改一改功能比如加上一个成绩模块或者操作日志把它发展成一个真正属于自己的项目。
返回列表