ARTICLE DETAIL

资讯详情

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

Java教室管理系统课程设计:Spring Boot + MVC + MySQL 全流程实战

Java教室管理系统课程设计:Spring Boot + MVC + MySQL 全流程实战 简介这份资源是面向计算机专业学生与Java初学者的一份教室管理系统毕业设计文档针对传统教室管理效率低、数据资源浪费等问题给出了一套可参考的完整实现方案。文档围绕B/S结构与MVC三层设计模式展开采用Java语言、myeclipse编辑器与MySQL数据库开发涵盖系统用户管理、楼层信息管理、校内新闻管理、教室信息管理及登录退出等核心模块并附有摘要、目录、技术选型与系统测试等章节内容。资源包共1个docx文件约1.07MB结构完整便于直接查阅与借鉴。目前已有43人学习下载。读者可从中获取需求分析思路、模块划分方式、数据库设计要点以及MVC分层与B/S架构的落地写法适合作为课程设计、毕业设计选题或Java Web入门练手项目的参考材料。1. 教室管理系统选型为什么 Java MVC MySQL 是课程设计里最稳的组合如果你正在做课程设计或毕业设计大概率会遇到这个题目基于 Java 的教室管理系统设计与实现。它听起来简单但真正动手时问题会一个接一个冒出来——教室状态怎么实时更新预约冲突怎么判断管理员、教师、学生三种角色怎么隔离权限我见过太多人一开始就卡在选型上纠结用 JSP 还是 Thymeleaf用原生 Servlet 还是 Spring Boot数据库用 MySQL 5.7 还是 8.0。其实这个题目的核心诉求非常明确你要在有限时间内交付一个能跑、能演示、能讲清楚 MVC 分层、能体现数据库设计能力的系统。Java Spring Boot MyBatis MySQL 这套组合是目前高校和企业里最通用的方案资料多、坑少、面试也能聊。它适合计算机相关专业的本科生也适合想补一个完整 Web 项目经验的 Java 初学者。接下来的内容我会按实际开发顺序把环境搭建、表结构设计、MVC 分层、预约冲突检测、权限拦截、部署排错全部串一遍让你能照着复现。2. 环境搭建与数据库设计从 MySQL 建库到 Spring Boot 工程初始化2.1 开发环境版本选择与安装要点这个项目对版本不敏感但有几个组合是经过大量验证的。JDK 用 8 或 11 都行Spring Boot 2.7.x 对 JDK 8 兼容最好如果你用 JDK 17就选 Spring Boot 3.x但要注意 MyBatis 版本也要跟着升。MySQL 建议 5.7.44 或 8.0.x5.7 的安装包更小8.0 的默认字符集是 utf8mb4省去改配置的麻烦。Maven 用 3.6 以上IDEA 社区版就够。安装 MySQL 时最容易翻车的地方是 root 密码和时区。Windows 下用 MSI 安装勾选 MySQL Server 和 MySQL Workbench 即可端口保持 3306。安装完成后在命令行执行mysql -u root -p能进去就说明服务正常。如果报Access denied说明密码错了需要重新配置。Linux 下用 apt 或 yum 安装后记得执行sudo mysql_secure_installation设置密码。提示MySQL 8.0 连接时如果报Public Key Retrieval is not allowed在 JDBC URL 后面加allowPublicKeyRetrievaltrueuseSSLfalse即可。2.2 教室管理系统的表结构设计数据库设计是这个项目的骨架。核心表不多但字段要考虑周全。我一般会建五张表用户表、教室表、预约记录表、角色表、日志表。用户表存账号密码和角色 ID教室表存教室编号、容量、位置、设备状态预约记录表存谁在什么时间段约了哪间教室日志表记录关键操作。-- 创建数据库字符集用 utf8mb4 避免中文乱码 CREATE DATABASE classroom_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE classroom_db; -- 用户表role 字段区分管理员(1)、教师(2)、学生(3) CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(50), role TINYINT NOT NULL DEFAULT 3, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 教室表status 表示可用(1)、维修中(0) CREATE TABLE t_classroom ( id INT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(20) NOT NULL UNIQUE, capacity INT NOT NULL, location VARCHAR(100), status TINYINT DEFAULT 1, remark VARCHAR(255) ); -- 预约表start_time 和 end_time 用于冲突检测 CREATE TABLE t_reservation ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, room_id INT NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, purpose VARCHAR(200), status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES t_user(id), FOREIGN KEY (room_id) REFERENCES t_classroom(id) );建表时注意三点一是预约表的start_time和end_time都要建索引后面冲突查询会频繁用到二是外键约束在课程设计里可以加方便演示数据完整性但生产环境通常用逻辑外键三是所有时间字段用DATETIME而不是TIMESTAMP避免时区转换带来的玄学问题。2.3 Spring Boot 工程初始化与依赖配置用 IDEA 的 Spring Initializr 创建工程勾选 Spring Web、MyBatis Framework、MySQL Driver、Lombok。如果手动建 Maven 工程在pom.xml里加以下依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies !-- Web MVC 核心 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis 整合 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Lombok 简化实体类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesapplication.yml配置数据源和 MyBatis 映射路径server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/classroom_db?useUnicodetruecharacterEncodingutf8allowPublicKeyRetrievaltrueuseSSLfalse username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.classroom.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case: true这个配置很关键它让数据库的room_no自动映射到 Java 的roomNo省去大量手动映射。mapper-locations指向 XML 文件目录如果你用纯注解方式写 SQL这行可以去掉但复杂查询还是 XML 更清晰。3. MVC 分层落地Controller、Service、Mapper 的职责边界与代码实现3.1 实体类与 Mapper 层的映射写法实体类用 Lombok 的Data注解字段名和数据库列名保持驼峰对应。以教室为例package com.example.classroom.entity; import lombok.Data; import java.time.LocalDateTime; Data public class Classroom { private Integer id; private String roomNo; private Integer capacity; private String location; private Integer status; private String remark; }Mapper 接口定义基础 CRUD复杂查询写在 XML 里。这里以预约冲突检测为例这是整个系统最核心的 SQLpackage com.example.classroom.mapper; import com.example.classroom.entity.Reservation; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.time.LocalDateTime; import java.util.List; Mapper public interface ReservationMapper { // 查询某教室在指定时间段内是否有冲突预约 int countConflict(Param(roomId) Integer roomId, Param(start) LocalDateTime start, Param(end) LocalDateTime end); int insert(Reservation reservation); ListReservation selectByUserId(Integer userId); }对应的 XML 映射文件?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.classroom.mapper.ReservationMapper !-- 冲突判断逻辑新预约的开始时间小于已有预约的结束时间且新预约的结束时间大于已有预约的开始时间 -- select idcountConflict resultTypeint SELECT COUNT(*) FROM t_reservation WHERE room_id #{roomId} AND status 1 AND start_time lt; #{end} AND end_time gt; #{start} /select insert idinsert useGeneratedKeystrue keyPropertyid INSERT INTO t_reservation(user_id, room_id, start_time, end_time, purpose, status) VALUES(#{userId}, #{roomId}, #{startTime}, #{endTime}, #{purpose}, 1) /insert /mapper冲突判断的 SQL 条件start_time #{end} AND end_time #{start}是区间重叠的标准写法。注意 XML 里要写成lt;写成gt;否则解析报错。这个查询走room_id索引数据量不大时性能没问题如果预约表超过十万行建议加(room_id, start_time, end_time)联合索引。3.2 Service 层的业务逻辑与事务控制Service 层负责业务规则比如预约时先检查教室是否存在、是否在维修、时间段是否冲突。加上Transactional保证插入失败时回滚。package com.example.classroom.service; import com.example.classroom.entity.Reservation; import com.example.classroom.mapper.ClassroomMapper; import com.example.classroom.mapper.ReservationMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; Service public class ReservationService { Autowired private ReservationMapper reservationMapper; Autowired private ClassroomMapper classroomMapper; Transactional public String book(Integer userId, Integer roomId, LocalDateTime start, LocalDateTime end, String purpose) { // 1. 校验时间合法性 if (start.isAfter(end) || start.isBefore(LocalDateTime.now())) { return 时间段不合法; } // 2. 校验教室状态 if (classroomMapper.selectById(roomId).getStatus() 0) { return 教室维修中暂不可预约; } // 3. 冲突检测 int conflict reservationMapper.countConflict(roomId, start, end); if (conflict 0) { return 该时间段已被预约; } // 4. 写入预约记录 Reservation r new Reservation(); r.setUserId(userId); r.setRoomId(roomId); r.setStartTime(start); r.setEndTime(end); r.setPurpose(purpose); reservationMapper.insert(r); return 预约成功; } }这段代码里Transactional保证冲突检测和插入在同一个事务里避免并发场景下两个请求同时通过检测然后都插入。虽然课程设计并发量低但面试官很可能会问这个问题写上事务是加分项。返回字符串而不是抛异常是为了前端好处理实际项目里可以用自定义异常加全局异常处理器。3.3 Controller 层的请求接收与参数校验Controller 只负责接收参数、调用 Service、返回结果。用RestController返回 JSON前端用 Ajax 调用。package com.example.classroom.controller; import com.example.classroom.service.ReservationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; RestController RequestMapping(/api/reservation) public class ReservationController { Autowired private ReservationService reservationService; PostMapping(/book) public MapString, Object book( RequestParam Integer userId, RequestParam Integer roomId, RequestParam DateTimeFormat(pattern yyyy-MM-dd HH:mm:ss) LocalDateTime start, RequestParam DateTimeFormat(pattern yyyy-MM-dd HH:mm:ss) LocalDateTime end, RequestParam(required false) String purpose) { String result reservationService.book(userId, roomId, start, end, purpose); MapString, Object map new HashMap(); map.put(code, result.equals(预约成功) ? 200 : 400); map.put(msg, result); return map; } }DateTimeFormat注解指定前端传的时间格式如果不加Spring 默认按 ISO 格式解析前端传2025-01-01 08:00:00会报 400 错误。这是新手最常见的翻车点之一。参数校验用RequestParam(required false)控制必填项更复杂的校验可以用Valid加 JSR-303 注解。4. 预约冲突与权限拦截两个最容易出 Bug 的模块怎么排查4.1 时间段冲突检测的边界条件冲突检测看起来简单但边界条件很容易写错。假设已有预约是 8:00-10:00新预约是 10:00-12:00这算不算冲突按start_time end AND end_time start的逻辑10:00 10:00 为 false所以不算冲突这是正确的因为 10:00 刚好是结束时间可以无缝衔接。但如果新预约是 9:59-12:009:59 10:00 为 true10:00 9:59 为 true判定冲突也正确。真正容易出错的是前端传参。如果前端传的是字符串2025-01-01 08:00少了秒DateTimeFormat(pattern yyyy-MM-dd HH:mm:ss)解析会失败。解决办法是前端统一传完整格式或者后端用DateTimeFormat(pattern yyyy-MM-dd HH:mm)兼容。另一个坑是时区如果服务器和数据库时区不一致存进去的时间会偏移 8 小时。在 JDBC URL 里加serverTimezoneAsia/Shanghai可以固定时区。4.2 基于拦截器的角色权限控制系统有三种角色管理员能管教室和用户教师能预约学生只能查看。用 Spring MVC 拦截器实现权限校验比在每个 Controller 里写 if-else 优雅得多。package com.example.classroom.interceptor; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object user session.getAttribute(loginUser); if (user null) { response.setStatus(401); response.getWriter().write({\code\:401,\msg\:\请先登录\}); return false; } // 管理员接口校验角色 String uri request.getRequestURI(); if (uri.startsWith(/api/admin)) { // 假设 session 里存了 role 字段 Integer role (Integer) session.getAttribute(role); if (role null || role ! 1) { response.setStatus(403); response.getWriter().write({\code\:403,\msg\:\无权限\}); return false; } } return true; } }注册拦截器Configuration public class WebConfig implements WebMvcConfigurer { Autowired private AuthInterceptor authInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/login, /api/register); } }addPathPatterns(/api/**)拦截所有 APIexcludePathPatterns放行登录注册。注意拦截器里返回 401 或 403 时前端要能识别并跳转登录页否则用户会看到一片空白。4.3 登录会话与密码存储登录用 Session 存用户信息是最简单的方案适合课程设计。密码不要明文存用 MD5 加盐或者 BCrypt。Spring Security 的 BCryptPasswordEncoder 可以直接用但引入 Spring Security 会让配置变复杂课程设计里用DigestUtils.md5DigestAsHex加固定盐也够用。import org.springframework.util.DigestUtils; public class PasswordUtil { private static final String SALT classroom_2025; public static String encode(String raw) { return DigestUtils.md5DigestAsHex((raw SALT).getBytes()); } public static boolean match(String raw, String encoded) { return encode(raw).equals(encoded); } }登录成功后session.setAttribute(loginUser, user)同时把role也存进去拦截器才能判断权限。Session 默认 30 分钟过期可以在application.yml里改server.servlet.session.timeout60m。5. 避坑与排查教室管理系统开发中最容易翻车的 5 个问题5.1 中文乱码从数据库到前端全链路排查现象教室名称、预约用途显示成问号或乱码。原因通常有三层数据库字符集不是 utf8mb4、JDBC URL 没加 characterEncoding、前端页面没声明 UTF-8。解决顺序是先用SHOW VARIABLES LIKE character%确认数据库字符集如果不是 utf8mb4 就改my.ini或my.cnf重启然后检查 JDBC URL 是否带useUnicodetruecharacterEncodingutf8最后在 HTML 的meta charsetUTF-8和 Controller 的produces application/json;charsetUTF-8上确认。5.2 预约时间存进去差 8 小时现象前端选的是 14:00数据库里存的是 06:00。原因是 MySQL 的时区是 UTCJava 的 LocalDateTime 没有时区信息JDBC 驱动按默认时区转换。解决办法是在 JDBC URL 里加serverTimezoneAsia/Shanghai或者把 MySQL 全局时区设为08:00。如果已经存了错误数据用UPDATE t_reservation SET start_time DATE_ADD(start_time, INTERVAL 8 HOUR)修正。5.3 MyBatis 查询返回 null 但数据库有数据现象Mapper 方法返回 null但直接在 MySQL 里执行 SQL 能查到。原因通常是字段名和实体类属性名不匹配比如数据库是room_no实体类是roomNo而map-underscore-to-camel-case没开启。检查application.yml里mybatis.configuration.map-underscore-to-camel-case: true是否配置。另一个可能是MapperScan没加Mapper 接口没被扫描到启动时不会报错但调用时注入失败。5.4 拦截器导致静态资源 404现象加了拦截器后CSS、JS、图片全部加载失败。原因是addPathPatterns(/**)拦截了所有请求包括静态资源。解决是把拦截路径改成/api/**或者在excludePathPatterns里加上/static/**、/css/**、/js/**。Spring Boot 默认静态资源在src/main/resources/static下拦截器不应该管这些。5.5 并发预约导致重复插入现象两个学生同时预约同一间教室同一时间段系统都提示成功。原因是冲突检测和插入之间有间隙两个线程都通过了检测。解决办法是在countConflict查询时加FOR UPDATE行锁或者给t_reservation表加唯一索引UNIQUE KEY uk_room_time (room_id, start_time, end_time)插入重复时数据库直接报错Service 捕获异常返回“预约冲突”。课程设计里用唯一索引最简单面试时能聊到乐观锁和悲观锁的区别就更好了。6. 从能跑到能演示接口测试、数据初始化与答辩技巧系统写完之后别急着截图交差。先确保每个接口都能用 Postman 或 curl 跑通。登录接口返回 Session ID 后后续请求要带上同一个 Cookie否则拦截器会一直返回 401。我一般会写一个简单的test.http文件把登录、查教室、预约、取消预约的请求按顺序列出来点一下就能跑完整流程。数据初始化也很关键。答辩时如果数据库空空如也演示效果会很差。写一个data.sql放在src/main/resources下Spring Boot 启动时自动执行。插入一个管理员账号、五个教室、几条预约记录。注意data.sql默认在每次启动时都执行如果表里有数据会报主键冲突可以在配置里加spring.sql.init.modenever关掉需要时手动执行。-- 初始化管理员密码是 123456 经过 MD5 加盐后的值 INSERT INTO t_user(username, password, real_name, role) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, 系统管理员, 1); INSERT INTO t_classroom(room_no, capacity, location, status) VALUES (A101, 60, 教学楼A区1楼, 1), (A102, 45, 教学楼A区1楼, 1), (B201, 120, 教学楼B区2楼, 1), (B202, 80, 教学楼B区2楼, 0), (C301, 30, 实验楼C区3楼, 1);答辩时老师最爱问三个问题你的 MVC 是怎么分层的预约冲突怎么判断的权限怎么控制的把这三段代码提前准备好能对着屏幕讲清楚Controller接参、Service校验、Mapper查库的完整链路。如果老师问“并发怎么办”就答唯一索引加事务再补一句“更高并发可以用 Redis 分布式锁”基本就稳了。最后说一个我自己的习惯每次改完 Mapper XML 之后一定重启服务再测因为 MyBatis 的 XML 是启动时加载的热部署有时候不生效不重启就会遇到“改了 SQL 但结果没变”的玄学问题。这个坑我踩过不止一次后来养成了改 XML 必重启的习惯省了很多排查时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表