拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Java+MySQL学生信息管理系统实战:JDBC连接池与数据库设计避坑指南

Java+MySQL学生信息管理系统实战:JDBC连接池与数据库设计避坑指南 简介这是一份基于Java Swing与MySQL实现的学生信息管理系统项目资源定位为Java初学者、在校学生以及需要完成课程设计或毕业设计的开发者。系统围绕管理员登录与学生信息管理展开覆盖增加、删除、修改、查询、列表展示等常规操作并配有数据库初始化脚本通过这套代码可以理解桌面图形界面与关系型数据库之间的完整交互流程。压缩包共48个文件包含15个Java源文件、22个class编译产物、3个jar依赖库、1个SQL脚本和4张界面截图整体体积约1.04MB目录结构明确涉及源码目录、资源目录、依赖库目录和数据库脚本等模块便于导入开发环境后直接运行、断点调试或二次开发扩展。附带界面展示截图可帮助对照验证功能效果数据库脚本也能显著降低环境准备难度适合初学者在课程设计中直接参考或改造。该资源已有2292人学习/下载适合快速掌握学生信息管理中增删改查功能的后台编码与界面联调思路。1. 为什么“Java MySQL 学生信息管理系统”至今仍是练手首选期末课设、毕业设计、简历项目三条赛道里“java和mysql数据库学生信息管理系统”几乎年年霸榜。一个用 Java 写业务逻辑、用 MySQL 存学生数据的增删改查项目看起来朴素但它把 JDBC、连接池、SQL、三层架构、事务这些 Java 后端最常用的零件全串起来了。做完它你再去碰 SSM 或 Spring Boot会发现很多概念只是换了个壳。这套选题能火还有一个现实原因学生信息管理系统的实体关系足够清晰学生、课程、成绩三张表就能撑起一个完整业务闭环数据库设计不会一上来就被复杂的权限模型劝退。适合没做过完整 CRUD 项目的人也适合想快速回顾 Java Web 基础的老手。我见过不少人把课设做成“全家桶”——又是 Redis 又是 Elasticsearch最后连部署都很痛苦。用 Java MySQL 老老实实把系统跑通技术上可能不出彩但能把基础砸实。2. JDBC 还是连接池先搞懂学生系统该用哪条路2.1 为什么连接池比每次新建 Connection 靠谱学生信息管理系统的并发量一般不高几十个人同时查成绩、改信息已经是极限。但即便是这种低并发场景我也强烈建议直接用连接池而不是用完就DriverManager.getConnection()关连接。原因很简单MySQL 建立一次连接要走 TCP 握手、认证、权限校验耗时在几十毫秒到上百毫秒如果每次查询都走一遍这个流程系统一有波动就很容易出现连接积压。用连接池相当于把连接“养”起来用时借、用完还。常见选择是 HikariCP 或 Druid。学生系统里数据量不大HikariCP 默认配置就够用代码也干净。Druid 自带监控页面课设答辩时能拿出监控截图确实是个加分项但配置项更多容易在细节上分心。2.2 最小 Maven 工程依赖版本别乱配先搭一个干净的 Maven 项目只放两个依赖MySQL 驱动和 HikariCP。Java 版本建议 8 以上MySQL 驱动用 8.0.x这样能兼容 MySQL 5.7 和 8.0 两个版本。properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target /properties dependencies dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version4.0.3/version /dependency /dependenciesmysql-connector-java的 8.0.33 是目前比较稳的版本再往上走要换 Google 的com.mysql:mysql-connector-j坐标没必要在练习项目里赶这个时髦。HikariCP 4.0.3 兼容 Java 8版本再高可能要求 Java 11。依赖版本这类东西工作里踩一次坑就长记性别图新先看编译环境的 JDK 版本。2.3 分层结构一个能让你少写一百行重复代码的约定网上很多学生系统把 JDBC 代码直接写在 Servlet 或者 JSP 里看起来是“跑通了”但后续加一个删除学生、改一个查询条件都要全局搜索 SQL。我一般会把工程分成dao、service、web三层dao层只负责数据库交互service层处理业务逻辑web层接收请求和返回结果。这个约定在课设阶段可能显得“多此一举”但实际写起来反而更轻松。比如添加学生时要校验学号是否重复校验逻辑写在 service查询代码写在 dao改起来互不干扰。等你以后做 Spring Boot 项目会发现这不就是Repository、Service、Controller的雏形只是 Jim 的注解换成了手动 new但思路是一样的。这种结构本身就是 Java 后端面试八股文里的必考题做完项目也算是对“三层架构”有了真操作不是只背概念。3. 设计学生信息核心表一张 student 表够不够3.1 从业务出发拆表别一上来就整 10 张表学生信息管理系统的核心业务无非是三类维护学生档案、维护课程、维护成绩。很多课设喜欢把“管理员”和“学生”分成两个用户表再设计一套复杂的权限表这其实没必要。如果系统只是给管理员录入、给学生查成绩一张user表加一个role字段就够了。权限设计是另一个课题堆在这里只会分散精力。我的建议是三张主表加一张关联表student存学生档案course存课程score存成绩。score表里带student_id和course_id外键这是最经典的选课成绩模型既能查某个学生的成绩单也能统计某门课的及格率。如果你还想加“班级”概念可以在student表里加一个class_name字段不必单独建班级表——除非你要做班级维度的统计报表。3.2 DDL 落地字段类型与索引的取舍下面是适合直接执行的建表 SQL。我用 MySQL 8.0 的默认存储引擎 InnoDB字符集用utf8mb4否则存 emoji 或者生僻字会乱码。CREATE DATABASE IF NOT EXISTS student_system DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE student_system; CREATE TABLE student ( id BIGINT AUTO_INCREMENT PRIMARY KEY, student_no VARCHAR(20) NOT NULL COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT DEFAULT 0 COMMENT 0-未知 1-男 2-女, birthday DATE COMMENT 出生日期, phone VARCHAR(20) COMMENT 手机号, class_name VARCHAR(50) COMMENT 班级, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_student_no (student_no) ) ENGINEInnoDB COMMENT学生信息表; CREATE TABLE course ( id BIGINT AUTO_INCREMENT PRIMARY KEY, course_no VARCHAR(20) NOT NULL COMMENT 课程编号, course_name VARCHAR(100) NOT NULL COMMENT 课程名称, credit DECIMAL(3,1) DEFAULT 0.0 COMMENT 学分, UNIQUE KEY uk_course_no (course_no) ) ENGINEInnoDB COMMENT课程表; CREATE TABLE score ( id BIGINT AUTO_INCREMENT PRIMARY KEY, student_id BIGINT NOT NULL, course_id BIGINT NOT NULL, score DECIMAL(5,2) COMMENT 成绩, exam_time DATETIME COMMENT 考试时间, UNIQUE KEY uk_student_course (student_id, course_id), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course(id) ) ENGINEInnoDB COMMENT成绩表;字段类型有三个值得注意的细节。第一student_no用VARCHAR(20)而不是INT因为学号可能以 0 开头数字类型会丢掉前导零。第二gender用TINYINT而不是CHAR(1)排序和比较都更方便。第三score用DECIMAL(5,2)如果以后要存“平时分、期末分”的加权结果两位小数足够用FLOAT会出现 89.99999 这种精度问题。索引也不要盲加。score表里的联合唯一索引uk_student_course既防止了同一学生同一课程重复录入又给“根据学生查所有成绩”的查询提供了索引。但student表的phone字段我刻意没加索引——这只是一个课设系统手机号查询频率极低没必要为每个字段都建索引索引写起来爽插入时维护成本是你的。3.3 范式与冗余不要为了“看起来专业”牺牲查询体验说到数据库设计和范式的关系优化到第三范式确实是标准做法但学生系统里有一条可以反着来如果查询学生成绩单时需要频繁关联student、course、score三张表连接查询写起来烦、慢也难排查。一个变通做法是在score表里冗余一个course_name字段避免每次查成绩都要 join 课程表。ALTER TABLE score ADD COLUMN course_name VARCHAR(100) COMMENT 冗余课程名称;这个冗余字段在写入成绩时由代码维护查询成绩单时只需要查score一张表。代价是如果课程改名要同步更新score表里的冗余字段。课设阶段无所谓但如果将来系统要做课程管理我还是建议去掉冗余用标准的三表关联。理解这个权衡比背范式的定义有用得多。4. 用 HikariCP 连接池跑通增删改查具体代码与参数说明4.1 初始化连接池参数别照抄先弄懂含义写一个DBUtil类来统一管理连接池静态代码块保证整个应用只初始化一次。package com.student.util; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import java.sql.Connection; import java.sql.SQLException; public class DBUtil { private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/student_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4); config.setUsername(root); config.setPassword(123456); config.setDriverClassName(com.mysql.cj.jdbc.Driver); config.setMaximumPoolSize(10); config.setMinimumIdle(2); config.setConnectionTimeout(30000); config.setIdleTimeout(600000); dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }setMaximumPoolSize(10)表示池子里最多 10 个连接这个值对课设系统足够了,性能测试中常见配置是最大连接数 CPU 核数 x 2 磁盘数量但课设不用算这么精确。setConnectionTimeout(30000)是指等连接的超时时间设置太短比如 3000在高并发时容易直接抛超时异常还看不出业务逻辑问题。JDBC URL 里的serverTimezoneAsia/Shanghai是 MySQL 8 驱动的硬性要求不写会报时区错误characterEncodingutf8mb4配合建表时的utf8mb4才能避免中文乱码——这算是最典型的 Java 课程设计踩坑点。4.2 DAO 层实现为什么必须用 PreparedStatement学生信息的增删改查核心是StudentDao。这里我强调一个原则所有 SQL 都必须用PreparedStatement禁止用字符串拼接 SQL。拼接 SQL 一旦遇到用户输入里的单引号轻则报错重则被 SQL 注入比如在搜索框输入 OR 11整个表的数据都可能被拉出来。package com.student.dao; import com.student.entity.Student; import com.student.util.DBUtil; import java.sql.*; import java.util.ArrayList; import java.util.List; public class StudentDao { public ListStudent listAll() throws SQLException { String sql SELECT id, student_no, name, gender, birthday, phone, class_name FROM student ORDER BY id DESC; ListStudent list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getLong(id)); s.setStudentNo(rs.getString(student_no)); s.setName(rs.getString(name)); s.setGender(rs.getInt(gender)); s.setBirthday(rs.getDate(birthday)); s.setPhone(rs.getString(phone)); s.setClassName(rs.getString(class_name)); list.add(s); } } return list; } public int insert(Student student) throws SQLException { String sql INSERT INTO student (student_no, name, gender, birthday, phone, class_name) VALUES (?, ?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, student.getStudentNo()); ps.setString(2, student.getName()); ps.setInt(3, student.getGender()); ps.setDate(4, student.getBirthday() null ? null : new java.sql.Date(student.getBirthday().getTime())); ps.setString(5, student.getPhone()); ps.setString(6, student.getClassName()); return ps.executeUpdate(); } } public int update(Student student) throws SQLException { String sql UPDATE student SET name?, gender?, birthday?, phone?, class_name? WHERE id?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, student.getName()); ps.setInt(2, student.getGender()); ps.setDate(3, student.getBirthday() null ? null : new java.sql.Date(student.getBirthday().getTime())); ps.setString(4, student.getPhone()); ps.setString(5, student.getClassName()); ps.setLong(6, student.getId()); return ps.executeUpdate(); } } }这段代码有三个细节值得展开。第一try-with-resources语法try (Connection conn ...)会自动关闭连接和语句不用手写finally { conn.close(); }。注意这里关闭连接并不是真的断开而是把连接归还给连接池供下一个人复用。第二birthday字段在 Java 实体里是java.util.Date但 MySQL 的DATE类型对应java.sql.Date所以要做一次转换。很多新手直接ps.setDate(4, student.getBirthday())编译都过不了报错信息还比较绕。第三rs.getLong(id)是按列名取值而不是按下标表结构调整时代码不易出错。4.3 service 层与业务校验学号重复这种坑早拦早省心DAO 层只做“增删改查”业务规则应该放 service。比如新增学生时学号不能重复如果直接在 DAO 的插入方法里查重划分就乱了。package com.student.service; import com.student.dao.StudentDao; import com.student.entity.Student; import java.sql.SQLException; import java.util.List; public class StudentService { private final StudentDao studentDao new StudentDao(); public boolean addStudent(Student student) throws SQLException { ListStudent exist studentDao.findByStudentNo(student.getStudentNo()); if (!exist.isEmpty()) { return false; } return studentDao.insert(student) 0; } }findByStudentNo在 DAO 里对应一条SELECT ... WHERE student_no ?走的是建表时定义的唯一索引uk_student_no查询效率高。这里即便没有 service 层做查重数据库的唯一索引也能兜底但会抛DuplicateKeyException之类的异常前端看到的就不是“学号已存在”而是“服务器错误”。把业务规则放在错误提示可控的地方是后端的基本功。5. 实战避坑笔记从环境到 SQL 的 5 条血泪经验5.1 MySQL 驱动加载失败错误 08001 与 ClassNotFoundException现象程序启动时爆ClassNotFoundException: com.mysql.jdbc.Driver或者连接时提示SQLException: No suitable driver found for jdbc:mysql://...。原因大部分是依赖没装对或者驱动类名写错了。MySQL 5.x 用com.mysql.jdbc.DriverMySQL 8.x 必须用com.mysql.cj.jdbc.Driver。如果 Maven 依赖引入失败~/.m2/repository下没有对应 jar自然会报找不到类。解决先确认pom.xml里mysql-connector-java版本是 8.0.x再执行mvn clean compile看依赖是否真的下载。驱动类名要跟版本匹配这是最常见也最让人无语的配置错误。5.2 连接报错error 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock现象Java 代码连不上命令行mysql -u root -p也连不上提示找不到 socket 文件。原因这个错误基本是 MySQL 服务没启动或者启动时指定了错误的 socket 路径。Linux 上如果手动用mysqld --socket/var/run/mysqld/mysqld.sock启动过Java 客户端用默认/tmp/mysql.sock找当然找不到。解决先用systemctl status mysql或service mysql status检查服务状态。确认服务活着再检查 JDBC URL 里是否要追加?socket/var/run/mysqld/mysqld.sock。实际操作中把localhost改成127.0.0.1也能绕过去——客户端会走 TCP 而不是本地 socket这套路在命令行连不上 MySQL 时很管用。5.3 中文乱码连接参数、表字符集、JSP 页面三处都要对齐现象插入的中文变成???或者查出来是乱码。原因最常见的是 JDBC URL 少了characterEncodingutf8mb4或者建表时用的默认latin1。我经常看到有人只改了一处比如在连接字符串里加了编码参数但表本身还是latin1字符集照样乱码。解决建库建表统一用utf8mb4JDBC URL 加characterEncodingutf8mb4如果页面用 JSP 还要在顶部写% page contentTypetext/html;charsetUTF-8 %。三处缺一不可。还有一个小坑MySQL 的characterEncodingutf8mb4里的utf8mb4是 Java 驱动的写法数据库客户端的SET NAMES utf8mb4也是必要的但这个要配合连接配置不是每次代码里手动执行。5.4 连接泄漏忘记关闭连接导致的Too many connections现象运行一段时间后报Cannot get a connection数据库端执行SHOW PROCESSLIST发现 Sleep 状态的连接占了几十个。原因代码里getConnection()之后没有在finally里关闭连接被“借走”不还池子里的连接逐渐耗尽。这个问题在课设里极其普遍因为很多新手只在try里写了conn.execute()根本没管后面。解决从头到尾用try-with-resources或者手写try { ... } finally { conn.close(); }。写完后全项目搜索DBUtil.getConnection()确认每一个调用点都有配套的关闭逻辑。连接池连接耗尽还有个隐形风险MySQL 端max_connections默认 151如果连接池最大连接数配置超过这个值再加上应用重启时的旧连接账号会被锁得很快。5.5 学号绑定与删除顺序外键约束在报错时永远比你想的严格现象删除一个学生时如果成绩表里有这个学生的记录会报Cannot delete or update a parent row: a foreign key constraint fails。原因score表的外键fk_score_student还在引用这个学生MySQL 默认会阻止主表记录被删。解决业务上要先删成绩再删学生。在 service 层写一个事务方法先执行DELETE FROM score WHERE student_id ?再执行DELETE FROM student WHERE id ?。如果不想每次删学生都分两步可以把外键改为ON DELETE CASCADE但这么做有风险一删成绩全没了容易误操作不建议课设里用。这个看起来是“误报错”的坑其实是提醒你数据库的约束永远比业务代码无情设计表关系时先问清楚删除联动。6. 从“做完”到“进阶”批量导入 Excel 成绩的实践系统跑通增删改查只是个开始。课设答辩时如果你能现场演示“用 Excel 批量导入学生成绩”效果会明显好过只能一条一条录。这也是我后期做得比较顺手的方向。实现思路很清晰前端上传.xlsx文件后端用 POI 解析再循环插入数据库。如果成绩数据有几千行逐条INSERT会很慢最直接的优化是改成批量插入。这里用 JDBC 的rewriteBatchedStatementstrue参数配合addBatch()可以显著提升效率。public int batchInsertScores(ListScore scores) throws SQLException { String sql INSERT INTO score (student_id, course_id, score) VALUES (?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { int count 0; for (Score s : scores) { ps.setLong(1, s.getStudentId()); ps.setLong(2, s.getCourseId()); ps.setBigDecimal(3, s.getScore()); ps.addBatch(); count; if (count % 500 0) { ps.executeBatch(); } } ps.executeBatch(); return count; } }这段代码的关键在rewriteBatchedStatementstrue。我建议把它加到 JDBC URL 里jdbc:mysql://localhost:3306/student_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4rewriteBatchedStatementstrue。没有这个参数时JDBC 驱动会把批量语句拆成单条逐发性能提升很有限加上后驱动会重写 SQL把多条插入拼成一条多 VALUES 语句发给 MySQL效果差距可以到几倍。每次executeBatch()的阈值设 500是因为积攒太多语句会占用内存太少又会频繁网络往返。批量插入前还有一道校验要补先查库里已有的(student_id, course_id)跳过重复的。用MERGE INTO ... ON DUPLICATE KEY UPDATE也行但课设代码里我更喜欢先查一遍再插逻辑透明出错了好排查。Excel 里的成绩是字符串比如89.5要先转BigDecimal遇到空值要跳过而不是转成 0——这算是最常见的“看着简单、做起来一堆边界”的坑。做这套东西时我的习惯是先把一条数据手动插入确认无误后再跑批量否则要么全插入成功要么因为一行脏数据抛异常回滚排查起来非常费劲。批量插入完成后可以用SELECT COUNT(*) FROM score对一下 Excel 的总行数这个校验很土但有效。最后再说一个让我记忆深刻的教训不要为了省事把 Excel 解析和数据校验混在一个方法里写各写各的出了错才好定位。学生信息管理系统看起来就是个 CRUD但把这一类基础问题想明白数据库、JDBC、事务这些基本功都会上一截。希望我的这些经验能帮到你。本文还有配套的精品资源点击获取
返回列表