ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

SSM选课系统课设毕设实战:架构、事务与部署全解析

SSM选课系统课设毕设实战:架构、事务与部署全解析 简介面向计算机相关专业毕业设计与 Java 实战练习这套基于 SSM 框架实现的学生选课信息管理系统覆盖学生、教师、管理员三种角色按不同身份划分操作权限集成学生管理、教师管理、课程管理等核心模块并支持按学号、学院、班级、专业等条件进行筛选过滤。技术实现上以后端 Spring、SpringMVC、MyBatis 为主前端采用 JSP、jQuery、CSS 页面交互数据库使用 MySQL 5.0 及以上开发环境兼容 JDK8、IDEA/Eclipse 与 Navicat前后台结构完整便于定位代码进行二次开发。资源压缩包约 22.79MB共 665 个文件以 JSP 页面、Java 控制类、XML 配置、class 文件、jar 依赖包和 SQL 数据库脚本为主同时包含 SVG 图标、JS/CSS 样式资源目录组织清晰适合用于课程设计、毕业设计或 SSM 整合学习也可作为相关功能模块的参考实现。目前已有 896 人浏览学习下载后可直接获得项目源码与数据库脚本便于梳理角色权限、数据表关系及选课业务实现细节也可在此基础之上扩展前端展示或增加业务功能。1. 为什么说SSM选课系统是课设和毕设的“标准答案”一套学生选课信息管理系统前端要处理学生、教师、管理员三个角色的信息维护后端要支撑选课、退课、课程安排这类强事务逻辑。用SSMSpring SpringMVC MyBatis来实现最直接的原因是它把控制层、业务层、持久层拆得足够清晰而且这类源码在eclipse、IDEA里的可运行性被验证过很多次对做毕业设计或者需要Java项目实战的人而言遇到问题能搜到大量同类方案。系统里AdminHandler、StudentHandler、TeacherHandler、CoursePlanHandler这些Handler命名风格的控制器本身就是典型的SpringMVC写法。你拿到源码后不需要重写架构重点是把数据库关系、权限控制和选课并发这三个点吃透这也是后续排错时最容易卡住的地方。2. SSM的请求分发、数据源与Mapper代理工程骨架拆解2.1 SpringMVC怎么把请求分发给Handler在SSM里SpringMVC负责HTTP请求的接收和转发。DispatcherServlet启动时读取spring-mvc.xml把有Controller注解的类注册成Handler比如项目里的StudentHandler就是一个持有学生增删改查方法的Controller。请求过来后HandlerMapping根据URL找到对应方法再通过视图解析器回到JSP页面。常见的web.xml配置里需要指定DispatcherServlet的url-pattern为/这样才能拦截所有后端请求。一个典型的配置片段如下servlet servlet-namespringmvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namespringmvc/servlet-name url-pattern//url-pattern /servlet-mappingload-on-startup1表示Tomcat启动时立刻初始化DispatcherServlet这样容器启动就能发现配置错误。如果这里把url-pattern写成*.do那么所有JSP页面的链接都需要带.do后缀项目改造会变得繁琐。2.2 Spring容器与MyBatis整合的边界Spring在这个系统里负责两件事管理service层的业务Bean以及把MyBatis的SqlSessionFactory注入给Mapper接口。实践中我会在applicationContext.xml里声明数据源和SqlSessionFactoryBean然后用MapperScannerConfigurer扫描mapper包让每个Mapper接口自动生成代理实现。这样做的好处是你在Service里直接注入StudentMapper不用自己写SqlSessionUtil之类的工具类。bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName valuecom.mysql.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/course_select?useUnicodetrueamp;characterEncodingutf8/ property nameusername valueroot/ property namepassword value123456/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property nameconfigLocation valueclasspath:mybatis-config.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecn.edu.course.mapper/ /bean注意url里的在XML中是合法的转义换成会导致解析失败。basePackage一般和Mapper接口所在包一致MyBatis会自动建立接口与XML中namespace的对应关系。这里用Druid数据源是为了后续监控连接池如果你本地不想引Druid换成org.springframework.jdbc.datasource.DriverManagerDataSource也可以跑通但生产环境别这么干。如果你习惯看源码可以直接翻mybatis-spring源码里MapperScannerConfigurer的processBeanDefinitions方法它会扫描basePackage下的接口给每个接口注册一个MapperFactoryBean。这个工厂Bean拿到SqlSessionTemplate后调用getMapper生成代理对象。这也是为什么在Service层里注入接口就能直接调用SQL。理解这一点后调试“找不到Mapper”之类的问题就不会只盯着兄弟节点看而是去检查基础包名是否覆盖到接口所在的包。2.3 工程文件该怎么对应拿到源码后第一件事不是急着启动而是对照工程结构确认各层位置。下面是SSM选课系统常见的包结构和职责对应关系包名承载内容对应技术层controllerStudentHandler、TeacherHandler、AdminHandler、CoursePlanHandlerSpringMVC控制层serviceStudentService、CourseService等Spring业务层mapperStudentMapper接口及StudentMapper.xmlMyBatis持久层domain/entityStudent、Course、CoursePlan数据库实体resourcesspring-mvc.xml、mybatis-config.xml、jdbc.properties配置文件这个映射关系在排错时非常有用如果JSP表单提交后500先看Handler方法里的RequestMapping路径是否和form action一致如果报Invalid bound statement就是Mapper接口和xml的namespace或id没对上。SSM框架源码看着复杂但顺着这个分层去查问题基本能控制在单层内。3. 学生选课系统的五张表与动态查询SQL设计3.1 核心表之间的关系学生选课系统的核心不是五张表而是student、teacher、course、course_plan、student_course这五张表之间的关系。student对应学生管理模块teacher对应教师管理模块course保存课程基础信息course_plan则记录由哪个教师在哪学期开设哪门课。学生选课时操作的是student_course中间表它保存student_id和plan_id这样同一个学生可以选多门课一门课也能被多个学生选择。数据库课程设计里最常见的错误是把课程和教师字段直接塞进学生选课记录里导致后续修改教师信息时数据冗余。表名作用关系student学生基本信息被student_course引用teacher教师基本信息被course_plan引用course课程基本信息被course_plan引用course_plan课程安排连接course、teacherstudent_course选课记录连接student、course_plan3.2 建表SQL及字段设计下面这段SQL是这套系统里可以直接执行的版本。为了兼容MySQL5.0以上字符集和索引我都按常规课设标准处理CREATE DATABASE course_select DEFAULT CHARACTER SET utf8; USE course_select; CREATE TABLE student ( student_id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) UNIQUE NOT NULL COMMENT 学号, name VARCHAR(30) NOT NULL, college VARCHAR(50) COMMENT 学院, major VARCHAR(50) COMMENT 专业, class_name VARCHAR(30) COMMENT 班级, password VARCHAR(64) DEFAULT 123456 ); CREATE TABLE teacher ( teacher_id INT PRIMARY KEY AUTO_INCREMENT, teacher_no VARCHAR(20) UNIQUE NOT NULL, name VARCHAR(30) NOT NULL, title VARCHAR(20) COMMENT 职称, department VARCHAR(50) ); CREATE TABLE course ( course_id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) UNIQUE NOT NULL, course_name VARCHAR(60) NOT NULL, credits DECIMAL(3,1) COMMENT 学分, hours INT COMMENT 总学时 ); CREATE TABLE course_plan ( plan_id INT PRIMARY KEY AUTO_INCREMENT, course_id INT NOT NULL, teacher_id INT NOT NULL, semester VARCHAR(20) NOT NULL COMMENT 如2024-2025-1, capacity INT DEFAULT 60, selected_count INT DEFAULT 0, CONSTRAINT fk_plan_course FOREIGN KEY (course_id) REFERENCES course(course_id), CONSTRAINT fk_plan_teacher FOREIGN KEY (teacher_id) REFERENCES teacher(teacher_id) ); CREATE TABLE student_course ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, plan_id INT NOT NULL, select_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_student_plan (student_id, plan_id), CONSTRAINT fk_sc_student FOREIGN KEY (student_id) REFERENCES student(student_id), CONSTRAINT fk_sc_plan FOREIGN KEY (plan_id) REFERENCES course_plan(plan_id) );这里的student_course表使用了unique key这是选课防重复的关键约束。如果不用数据库约束仅在Java程序里判断的话两套请求同时提交时就可能插入重复记录。course_plan中的capacity和selected_count是承载选课容量控制的两个字段不要把它们放到course表里因为同一门课在不同学期容量可能不同。参数说明student_no设置unique是为了满足信息管理系统中按学号查询过滤的需求外键fk_plan_course和fk_plan_teacher保证课程安排必须引用合法的课程和教师避免出现悬空的plan记录。MySQL5.0以上支持这些写法但需要注意如果使用默认的InnoDB引擎外键约束才生效。这段建表SQL在Navicat里直接运行即可命令行执行时要注意分号分隔。3.3 学生查询与动态SQL学生管理模块要求支持按学号、学院、班级、专业查询。常用的写法是Mapper里拼接动态SQLselect idfindByCondition resultTypecn.edu.course.domain.Student SELECT * FROM student where if teststudentNo ! null and studentNo ! AND student_no #{studentNo} /if if testcollege ! null and college ! AND college LIKE CONCAT(%, #{college}, %) /if if testmajor ! null and major ! AND major #{major} /if if testclassName ! null and className ! AND class_name #{className} /if /where /select这里用 标签而不是手动加WHERE 11一是避免前缀and导致的SQL语法问题二是MyBatis生成出来的SQL更干净。 中的null和空字符串判断要同时做因为前端表单未填写的字段往往是空串。学号查询用等号而学院和专业用模糊匹配因为学院可能输入简称模糊匹配更实用。实际在库表里如果查询量变大可以给college、major、class_name建立组合索引但课设阶段单表数据量少普通索引就够。4. 三种角色的Handler控制器与选课事务锁4.1 Handler控制器与登录拦截项目中的AdminHandler、StudentHandler、TeacherHandler分别处理三种角色的操作。登录验证可以放在Interceptor里统一检查session中保存的用户角色。先看登录控制器Controller RequestMapping(/login) public class LoginHandler { Autowired private StudentService studentService; Autowired private TeacherService teacherService; Autowired private AdminService adminService; RequestMapping(value /doLogin, method RequestMethod.POST) public String doLogin(String userId, String password, String role, HttpSession session, Model model) { Object user null; if (student.equals(role)) { user studentService.checkLogin(userId, password); } else if (teacher.equals(role)) { user teacherService.checkLogin(userId, password); } else { user adminService.checkLogin(userId, password); } if (user null) { model.addAttribute(msg, 用户名或密码错误); return login; } session.setAttribute(loginUser, user); session.setAttribute(role, role); if (admin.equals(role)) { return redirect:/admin/index; } return redirect:/student/home; } }登录成功后把用户对象和角色写进session拦截器检查session角色是否与请求路径匹配。参数userId用的是通用命名实际前端传来的可能是学号或教师工号。注意return的是逻辑视图名SpringMVC视图解析器会拼接前缀后缀变成/WEB-INF/jsp/login.jsp。使用redirect是为了避免表单重复提交刷新页面时不会再次触发POST。4.2 权限控制表角色可操作模块关键Handler备注管理员学生管理、教师管理、课程管理AdminHandler有增删改查权限教师查看课程安排、查看选课名单TeacherHandler通常无修改学生信息权限学生查看课程、选课、退课、查看个人课表StudentHandler、CoursePlanHandler选课要校验容量权限矩阵看起来简单但同一套SSM源码里很多漏洞出在管理员入口未做角色校验。比如StudentHandler的add方法如果不加拦截普通学生直接访问/student/add就能增加学生记录。我一般会在Handler类上加PreAuthorize注解配合Spring Security但课设项目里用拦截器按路径前缀控制更直接。4.3 选课与退课的事务边界选课操作涉及student_course的插入和course_plan.selected_count的更新必须放在一个事务里。下面这个Service方法展示了关键逻辑Service public class CoursePlanService { Autowired private CoursePlanMapper coursePlanMapper; Autowired private StudentCourseMapper studentCourseMapper; Transactional(rollbackFor Exception.class) public boolean selectCourse(Integer studentId, Integer planId) { // 1. 查询课程计划锁定当前行避免超选 CoursePlan plan coursePlanMapper.selectPlanForUpdate(planId); if (plan null) { throw new RuntimeException(课程安排不存在); } // 2. 判断是否已选 Integer count studentCourseMapper.countByStudentAndPlan(studentId, planId); if (count ! null count 0) { throw new RuntimeException(不能重复选课); } // 3. 判断容量 if (plan.getSelectedCount() plan.getCapacity()) { throw new RuntimeException(该课程已选满); } // 4. 插入记录并更新已选人数 studentCourseMapper.insert(studentId, planId); coursePlanMapper.increaseSelectedCount(planId); return true; } }Transactional注解让插入和update在同一个数据库事务内执行任何一步抛异常都会整体回滚。这里最关键的是selectPlanForUpdate它在数据库层面执行SELECT ... FOR UPDATE锁住course_plan表的这一行记录避免两个人同时选最后一门课导致超选。如果课设里没有这个锁仅仅靠Java代码判断容量高并发时会超卖。副作用是这个方法会让同一门课的选课请求串行化但课设场景学生选课并发不高这个代价可以接受。MyBatis对应的mapper接口Select(SELECT * FROM course_plan WHERE plan_id #{planId} FOR UPDATE) CoursePlan selectPlanForUpdate(Integer planId); Insert(INSERT INTO student_course(student_id, plan_id) VALUES(#{studentId}, #{planId})) int insert(Param(studentId) Integer studentId, Param(planId) Integer planId); Update(UPDATE course_plan SET selected_count selected_count 1 WHERE plan_id #{planId}) int increaseSelectedCount(Integer planId);这里用注解SQL是为了演示便捷项目里的StudentCourseMapper.xml也可以写成XML形式。FOR UPDATE语句必须和事务同时使用才有意义如果你不在Service方法加TransactionalselectPlanForUpdate执行完提交后锁就释放了后续插入就不受保护。5. 在Eclipse/IDEA里跑起来部署顺序与SSM常见报错5.1 导入源码与启动前检查拿到源码后前后端源码要区分清楚。用Eclipse导入时选择Existing Projects into Workspace用IDEA则直接Open项目目录等待Maven或lib包加载。这个项目如果不使用Maven需要把lib目录下的jar包全部Build Path如果是Maven项目先执行mvn clean compile确认依赖完整。特别注意源码里的jdbc.properties中的数据库地址、用户名、密码要改成你自己的否则启动后MyBatis会报数据库连接失败。5.2 启动Tomcat部署war把项目右键Export成war包可以放到Tomcat的webapps目录下然后启动Tomcatcd /path/to/apache-tomcat-8.5/bin ./startup.sh # 查看日志 tail -f ../logs/catalina.outWindows下执行startup.bat。Tomcat解压war包后会自动生成同名目录访问地址一般是http://localhost:8080/项目名/login.jsp。如果端口被占用在conf/server.xml里修改Connector的port。这里需要注意Tomcat版本和JDK8的匹配Tomcat 8.5、9都可以Tomcat 10以上因为Servlet API用jakarta命名空间和这个SSM项目不兼容。5.3 常见系统报错与修复以数据库连接为例MySQL 8.x下如果还在用旧驱动会报ClassNotFoundException或Unable to load authentication plugin需要引入jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/course_select?serverTimezoneAsia/ShanghaiuseSSLfalsecharacterEncodingutf8jdbc.url里的serverTimezone是MySQL 8必须加的参数否则日期字段会在连接时报错useSSLfalse能省去本机测试的SSL握手。项目原先可能配的是com.mysql.jdbc.Driver这是MySQL 5时代的类名MySQL 8下虽然短暂兼容但会打印警告建议直接换成上面的cj驱动。还有一个坑是页面表单提交后返回404。检查Handler方法上的RequestMapping路径是否与前端form的action完全一致同时确认spring-mvc.xml里配了 mvc:annotation-driven/ 。如果缺少这个配置SpringMVC无法识别RequestMapping注解这也是SSM框架整合里最常见的问题。另外JSP页面里如果有大量静态资源CSS/JS要在spring-mvc.xml里放行静态资源mvc:default-servlet-handler/如果不放行样式表全部加载不出来界面会显得很乱但功能逻辑其实是通的。这也是很多人误以为系统有bug的根源。排错时先看Tomcat日志里有没有异常再按Controller层、Service层、Mapper层逐层打日志不要直接改数据库表。本文还有配套的精品资源点击获取
返回列表