Java+MySQL学生信息管理系统:从建表到分页查询的完整实现
发布时间:2026/9/28 14:59:50来源:尧图网络
简介本资源为基于JavaMySQL实现的学生信息管理系统课程设计项目编号100010724面向计算机相关专业学生及Java Web初学者可用于课程设计、毕业设计参考或Servlet与DAO分层架构的实战练习。项目在IntelliJ IDEA 2020.3.3下开发采用JavaBean、Servlet、DAO模式由Servlet处理请求并配置过滤器进行权限控制数据持久化使用MySQL。功能覆盖学生、教师、管理员三类角色学生可查询成绩、课程平均分、班级开课与教学计划教师可录入与查询成绩、查看任课情况管理员负责学生、教师、课程、成绩、学院、专业与班级等信息管理。压缩包共551个文件约17.88MB包含png与jpg界面截图、js与css前端资源、html与jsp页面、java源码与class编译文件、xml配置及jar依赖等结构完整便于二次开发。目前已有568人学习下载适合需要完整分层架构案例与数据库设计参考的读者。1. 从一份课程设计说起JavaMySQL 学生信息管理系统到底要解决什么每年毕业季和期末周搜索框里「javaweb项目完整案例mysql」「java课程设计案例源码」这类词都会集中爆发。点进去看绝大多数人卡的不是 Java 语法也不是 MySQL 装不上而是不知道一个能跑、能演示、能讲清楚的学生信息管理系统边界到底划在哪。这套基于 Java MySQL 的 Web 学生信息管理系统本质是一个典型的 CRUD 后台学生、班级、课程、成绩四类核心数据配上登录鉴权和分页查询。它不追求高并发也不碰分布式但要求你真正把「浏览器发请求 → 后端处理 → 数据库落盘 → 页面回显」这条链路走通一遍。适合谁正在做课程设计的学生、想补一个完整 Web 项目进简历的转行者以及需要一套可扩展后台骨架的初级开发者。下面我按自己实际搭过几遍的顺序把选型、建表、接口、页面和踩过的坑一次讲透。2. 技术选型与工程骨架为什么是 Servlet/JSP 而不是一上来就 Spring Boot2.1 先想清楚这套系统用原生 JavaWeb 还是 Spring Boot很多人一上手就问「能不能直接 Spring Boot MyBatis-Plus」能但要看你的目标。如果这是课程设计答辩老师大概率会问你「请求怎么进的 Servlet」「JDBC 连接什么时候关」这时候你用 Spring Boot 反而说不清底层。原生 Servlet JSP JDBC 的好处是链路透明每一层你都能看到坏处是样板代码多但正好逼你把基础打牢。我的建议是课程设计用 Servlet 3.1 JSP JDBC简历项目再往上叠 Spring Boot。两者数据库层可以完全复用迁移成本很低。选型确定后工程结构要提前定好不然后面改包名能改到崩溃。常见做法是按分层建包src/ └─ main/ ├─ java/ │ └─ com/example/sims/ │ ├─ controller/ // Servlet接收请求 │ ├─ service/ // 业务逻辑 │ ├─ dao/ // 数据库访问 │ ├─ entity/ // 实体类对应表 │ └─ util/ // DBUtil、MD5 等工具 ├─ resources/ │ └─ db.properties // 数据库连接配置 └─ webapp/ ├─ WEB-INF/ │ ├─ web.xml │ └─ views/ // JSP 页面 └─ static/ // css、js这个结构不是官方标准但它把「谁调用谁」写在了目录上。controller 只做参数校验和转发service 写业务规则dao 只碰 SQL。新手最容易犯的错是把 SQL 写在 Servlet 里后面加一个字段要改五六个文件。2.2 数据库连接别把 DriverManager 写死在代码里JDBC 连接是这套系统的命门。我见过太多人把 url、username、password 直接硬编码在 Java 文件里换台机器就报Access denied。正确做法是抽到db.properties用类加载器读取# src/main/resources/db.properties jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/sims?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.passwordyour_password注意serverTimezone这个参数MySQL 8 之后不写它连接时会直接抛时区异常这是新手第一个翻车点。useSSLfalse在本地开发可以关掉生产环境要单独配证书这里不展开。读取配置和获取连接的代码// DBUtil.java public class DBUtil { private static String url; private static String user; private static String password; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(in); Class.forName(props.getProperty(jdbc.driver)); url props.getProperty(jdbc.url); user props.getProperty(jdbc.username); password props.getProperty(jdbc.password); } catch (Exception e) { throw new ExceptionInInitializerError(数据库配置加载失败: e.getMessage()); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } // 关闭资源顺序不能反ResultSet - Statement - Connection public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }逻辑说明静态代码块只在类第一次加载时执行一次配置读一次就够。getConnection每次新建连接适合课程设计这种低并发场景。参数说明url里的characterEncodingutf8保证中文不乱码serverTimezone必须和数据库实际时区一致否则存进去的时间会差 8 小时。关闭顺序必须是 ResultSet → Statement → Connection反过来关会报错。提示如果后面要做连接池把getConnection换成从HikariDataSource取即可上层代码不用动这就是分层的好处。3. 建表与实体映射学生、班级、课程、成绩四张表怎么设计3.1 表结构设计主外键和字段类型的选择数据库设计决定了后面 SQL 好不好写。核心四张表student、clazz、course、score。学生属于班级成绩关联学生和课程这是典型的一对多和多对多。CREATE DATABASE IF NOT EXISTS sims DEFAULT CHARSET utf8mb4; USE sims; CREATE TABLE clazz ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 班级名称, grade VARCHAR(20) COMMENT 年级 ); CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(30) NOT NULL, gender TINYINT DEFAULT 1 COMMENT 1男 0女, clazz_id INT, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (clazz_id) REFERENCES clazz(id) ); CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, cname VARCHAR(50) NOT NULL, credit DECIMAL(3,1) DEFAULT 0 COMMENT 学分 ); CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,1) DEFAULT 0, UNIQUE KEY uk_stu_course (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (course_id) REFERENCES course(id) );参数说明utf8mb4而不是utf8因为后者在 MySQL 里是残缺的三字节实现存 emoji 或生僻字会报错。sno加 UNIQUE学号重复是业务红线。score表用联合唯一键uk_stu_course防止同一个学生同一门课录入两条成绩这个约束比在 Java 里判断靠谱得多。gender用 TINYINT 存 0/1比 VARCHAR 省空间也避免「男/男性/Male」这种脏数据。3.2 实体类与 DAO把 ResultSet 映射成对象实体类字段名和表字段对应用驼峰命名。DAO 层负责把 ResultSet 转成对象这里以学生查询为例// StudentDao.java public ListStudent findByPage(String keyword, int page, int size) { ListStudent list new ArrayList(); String sql SELECT s.*, c.name AS clazzName FROM student s LEFT JOIN clazz c ON s.clazz_id c.id WHERE s.name LIKE ? OR s.sno LIKE ? ORDER BY s.id DESC LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setString(2, % keyword %); ps.setInt(3, (page - 1) * size); ps.setInt(4, size); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setSno(rs.getString(sno)); s.setName(rs.getString(name)); s.setGender(rs.getInt(gender)); s.setClazzName(rs.getString(clazzName)); list.add(s); } } } catch (SQLException e) { throw new RuntimeException(查询学生失败, e); } return list; }逻辑说明用PreparedStatement而不是拼接字符串这是防 SQL 注入的底线keyword里的单引号不会破坏语句结构。LEFT JOIN保证即使学生没分班也能查出来。分页用LIMIT offset, sizeoffset 从 0 开始所以是(page-1)*size。参数说明size一般设 10 或 15太大页面卡太小翻页烦。ORDER BY s.id DESC让新增的学生排前面符合直觉。注意LIKE的模糊查询在数据量大时会全表扫描课程设计无所谓真上生产要给name建索引或换全文检索。4. 接口与页面打通登录鉴权、分页查询和成绩录入4.1 登录鉴权Session 怎么用才不出安全漏洞登录是这套系统唯一的安全入口。流程是用户提交账号密码 → 后端查库比对 → 成功则把用户信息写进 Session → 后续请求用 Filter 拦截校验。// LoginServlet.java 核心片段 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); // 密码做 MD5 后再比对数据库存的是密文 String md5Pwd MD5Util.encode(password); UserDao dao new UserDao(); User user dao.findByUsernameAndPwd(username, md5Pwd); if (user ! null) { req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /student/list); } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/WEB-INF/views/login.jsp).forward(req, resp); } }逻辑说明密码绝不能明文存库MD5 虽然现在不够强但课程设计够用至少比明文强。登录成功后用sendRedirect而不是forward避免刷新页面重复提交表单。参数说明req.getContextPath()拿到项目部署路径保证跳转地址正确不写死/sims。配套的登录拦截 Filterpublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; String uri req.getRequestURI(); // 登录页和静态资源放行 if (uri.endsWith(login.jsp) || uri.contains(/static/) || uri.endsWith(/login)) { chain.doFilter(request, response); return; } if (req.getSession().getAttribute(loginUser) null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); }逻辑说明白名单放行登录相关路径其余一律检查 Session。参数说明uri.contains(/static/)让 css、js 不被拦截否则登录页样式全丢。这个 Filter 要在web.xml里配置拦截/*。4.2 成绩录入与列表回显一次请求的完整链路成绩录入是业务最复杂的一环要同时处理学生、课程两个下拉框和分数校验。前端 JSP 提交后后端先校验分数范围再入库。// ScoreServlet.java 保存成绩 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int studentId Integer.parseInt(req.getParameter(studentId)); int courseId Integer.parseInt(req.getParameter(courseId)); double score Double.parseDouble(req.getParameter(score)); if (score 0 || score 100) { req.setAttribute(msg, 分数必须在 0-100 之间); req.getRequestDispatcher(/WEB-INF/views/score/add.jsp).forward(req, resp); return; } ScoreDao dao new ScoreDao(); // 存在则更新不存在则插入 dao.saveOrUpdate(studentId, courseId, score); resp.sendRedirect(req.getContextPath() /score/list); }逻辑说明先做业务校验再落库避免脏数据。saveOrUpdate内部用INSERT ... ON DUPLICATE KEY UPDATE依赖前面建的联合唯一键一条 SQL 搞定新增和修改。参数说明studentId、courseId从下拉框传过来是字符串要转 intscore转 double 后校验范围。对应的 SQLINSERT INTO score (student_id, course_id, score) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE score VALUES(score);逻辑说明MySQL 特有语法遇到唯一键冲突就执行 UPDATE省去先查后判的两次数据库交互。参数说明VALUES(score)取的是本次要插入的值MySQL 8.0.20 之后推荐用别名写法但课程设计用这个兼容性更好。提示列表页回显成绩时用 JOIN 把学生名和课程名一起查出来别在 JSP 里循环查库那是 N1 查询的经典翻车现场。5. 避坑与排查这套系统最容易翻车的五个地方5.1 中文乱码现象是页面显示问号原因是编码链路没统一现象提交表单后数据库里存的是???或者页面显示乱码。原因请求、响应、数据库三处编码不一致。解决分三步统一。第一JSP 页面头部写% page contentTypetext/html;charsetUTF-8 %第二Servlet 里在取参数前设置req.setCharacterEncoding(UTF-8)和resp.setContentType(text/html;charsetUTF-8)第三数据库连接 url 带characterEncodingutf8建库用utf8mb4。三处缺一处都会乱码这是血泪经验。5.2 连接泄漏现象是跑一会儿就报 Too many connections现象系统用一段时间后所有请求卡死日志报Too many connections。原因Connection、Statement、ResultSet用完没关连接池被占满。解决所有数据库操作必须放在 try-with-resources 里或者 finally 块中按 ResultSet → Statement → Connection 顺序关闭。排查方法在 MySQL 里执行SHOW PROCESSLIST;看是不是有一堆 Sleep 状态的连接堆着。5.3 时区异常现象是启动就报 The server time zone value is unrecognized现象MySQL 8 环境下连接直接失败报时区无法识别。原因MySQL 8 默认时区是 UTCJDBC 驱动要求显式指定。解决url 里加serverTimezoneAsia/Shanghai。如果还不行检查 MySQL 服务端my.cnf里的default-time-zone配置。这个坑在「mysql安装配置教程」里很少有人提但几乎人人都会遇到一次。5.4 外键约束报错现象是删班级时删不掉现象删除一个班级报Cannot delete or update a parent row。原因student表有外键指向clazz班级下还有学生时不允许删。解决要么先删或转移该班级下的学生要么把外键改成ON DELETE SET NULL。我的做法是在 Service 层先查该班级有没有学生有就提示用户而不是直接抛数据库异常给前端。5.5 分页总数算错现象是最后一页多出空白或漏数据现象分页显示总页数不对最后一页要么空白要么少几条。原因总数查询和列表查询的 WHERE 条件不一致或者LIMIT的 offset 算错。解决总数 SQL 和列表 SQL 用完全相同的 WHERE 条件offset 严格用(page-1)*size。排查时把两条 SQL 都打印出来手动在 Navicat 里跑一遍对比结果比盯着代码看快得多。6. 进阶技巧用一条 SQL 查出班级成绩统计以及我踩过的验证习惯系统能跑之后真正拉开差距的是统计查询。答辩或演示时老师最爱问「能不能按班级算平均分」。很多人第一反应是在 Java 里循环累加数据一多就慢。正确做法是交给数据库SELECT c.name AS clazzName, COUNT(s.id) AS stuCount, ROUND(AVG(sc.score), 2) AS avgScore, MAX(sc.score) AS maxScore, MIN(sc.score) AS minScore FROM clazz c LEFT JOIN student s ON s.clazz_id c.id LEFT JOIN score sc ON sc.student_id s.id GROUP BY c.id, c.name ORDER BY avgScore DESC;逻辑说明三层 LEFT JOIN 把班级、学生、成绩串起来GROUP BY按班级聚合。用 LEFT JOIN 而不是 INNER JOIN保证没有学生的班级也能出现在结果里平均分显示 NULL 而不是整行消失。参数说明ROUND(..., 2)保留两位小数AVG会自动忽略 NULL 值所以没成绩的学生不影响平均分计算。这条 SQL 直接映射到一个 VO 类JSP 里遍历输出即可比在 Java 里写循环优雅得多。再补一个验证习惯每次改完 DAO 层的 SQL我都会先在 Navicat 或 MySQL 命令行里把参数替换成真实值跑一遍确认结果对了再写进 Java。因为 Java 里调试 SQL 要重启 Tomcat慢数据库客户端里改一下就能看结果快。这个习惯帮我省了无数时间。还有一个容易被忽略的点web.xml里的welcome-file-list要配好否则访问根路径会 404。我一般把登录页设为欢迎页用户一进来就是登录界面体验顺。另外JSP 页面尽量放WEB-INF/views/下这样不能通过 URL 直接访问必须走 Servlet 转发安全性高一层。最后说个我自己的教训早期做这套系统时我图省事把数据库密码写在了 DAO 的静态变量里结果把代码发给同学参考他那边数据库密码不一样跑不起来还以为是代码有 bug查了一下午。从那以后凡是和环境相关的配置一律抽到 properties 文件代码里只留读取逻辑。这个习惯后来救了我很多次。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网