简介这份资源是面向高校计算机相关专业学生的数据库课程设计参考资料以「学生信息管理系统」为课题帮助读者将关系数据库理论转化为实际开发能力适合正在完成课程设计或需要练习 Java 与 MySQL 综合应用的学习者。压缩包内共 1 个 PDF 文件约 2.47MB为完整的课程设计报告内容涵盖课程设计题目、目的、总体设计、详细设计、结果与分析及小结心得等章节。报告围绕 MySQL 数据库设计展开涉及学生表、学籍变更表、奖励表、处罚表等实体关系的构建以及主键外键约束下的数据一致性维护同时讲解 Java 通过 JDBC 连接 MySQL 的关键技术包括驱动加载、连接建立以及基于 Statement 与 PreparedStatement 的查询、插入、更新、删除操作并给出学生信息录入、学籍变更、奖励处罚记录、个人情况查询修改等模块化设计思路。目前已有 942 人学习可作为课程设计报告撰写与系统实现的参考范例。1. 从一份课程设计报告说起JavaMySQL 学生信息管理系统到底能跑出什么如果你手头正躺着一份数据库课程设计任务书要求用 Java 连 MySQL 做一个学生信息管理系统那这份资源就是冲着你来的。它不是那种只给几张 ER 图、几段伪代码的“纸面报告”而是一套能跑起来的完整工程Java Swing 做界面MySQL 5.5 做数据仓库JDBC 做桥梁覆盖学生个人信息录入、学籍变更、奖励处罚、查询修改这几大模块。适合谁正在赶课程设计 deadline 的计算机专业学生以及想拿一个“麻雀虽小五脏俱全”的 CRUD 项目练手 JDBC 和 Swing 的初学者。它解决的核心问题很具体让你不用从零搭架子直接拿到一套可编译、可运行、可改表结构的代码底子把精力放在理解数据库设计和 SQL 操作上而不是耗在环境配置和界面拖拽上。下面我按“资源是什么→怎么用→坑在哪”的顺序把它拆开讲透。2. 数据库设计与 JDBC 连接先把表建对再谈增删改查2.1 学生信息管理系统的表结构怎么定这套系统的数据层围绕“学生”这个核心实体展开常见做法是拆成四张表学生表、学籍变更表、奖励表、处罚表。学生表存基础信息学籍变更表记录转系、休学、复学、毕业这类流动情况奖励表和处罚表分别挂奖惩记录。表与表之间通过学号或学生 ID 关联主键用自增整数外键指向学生表的主键保证一个学生的奖惩记录不会挂到别人头上。下面是我根据报告里的字段描述整理出的建表 SQL字段类型和约束都按 MySQL 5.5 的语法来写可以直接在 Navicat 或命令行里执行-- 学生表核心实体存基础信息 CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, -- 学生ID自增主键 name VARCHAR(50) NOT NULL, -- 姓名 sex VARCHAR(4) DEFAULT 男, -- 性别 age INT, -- 年龄 telephone VARCHAR(20), -- 联系电话 classid INT, -- 班级ID关联班级表 stdesc VARCHAR(200), -- 备注描述 FOREIGN KEY (classid) REFERENCES t_class(id) ) ENGINEInnoDB DEFAULT CHARSETutf8; -- 班级表学生所属班级 CREATE TABLE t_class ( id INT PRIMARY KEY AUTO_INCREMENT, classname VARCHAR(50) NOT NULL, projectid INT -- 所属专业ID ) ENGINEInnoDB DEFAULT CHARSETutf8; -- 学籍变更表记录转系、休学、复学、毕业 CREATE TABLE t_status_change ( id INT PRIMARY KEY AUTO_INCREMENT, studentid INT NOT NULL, change_type VARCHAR(20), -- 变更类型 change_date DATE, -- 变更日期 remark VARCHAR(200), FOREIGN KEY (studentid) REFERENCES t_student(id) ) ENGINEInnoDB DEFAULT CHARSETutf8; -- 奖励表 CREATE TABLE t_award ( id INT PRIMARY KEY AUTO_INCREMENT, studentid INT NOT NULL, award_name VARCHAR(100), award_date DATE, FOREIGN KEY (studentid) REFERENCES t_student(id) ) ENGINEInnoDB DEFAULT CHARSETutf8; -- 处罚表 CREATE TABLE t_punish ( id INT PRIMARY KEY AUTO_INCREMENT, studentid INT NOT NULL, punish_name VARCHAR(100), punish_date DATE, FOREIGN KEY (studentid) REFERENCES t_student(id) ) ENGINEInnoDB DEFAULT CHARSETutf8;这里有几个参数值得注意。ENGINEInnoDB是必须的因为只有 InnoDB 支持外键约束MyISAM 建了外键也不会生效。CHARSETutf8是为了存中文姓名和备注时不出现乱码MySQL 5.5 默认字符集不一定是 utf8建库时最好显式指定。classid和projectid这种关联字段类型要和被引用表的主键保持一致否则外键创建会报 errno 150。2.2 JDBC 驱动加载与连接封装报告里用的是mysql-connector-java-5.1.8-bin.jar这个版本对应 MySQL 5.5 是匹配的。在 Eclipse 里导入 jar 包的步骤是右键项目 → Build Path → Configure Build Path → Libraries → Add External JARs选中 jar 包后确定。这一步不做运行时会直接抛ClassNotFoundException。连接数据库的核心代码封装在DataCon类里我把它补全成可运行的版本import java.sql.Connection; import java.sql.DriverManager; public class DataCon { // 数据库驱动类名MySQL 5.x 用这个 private static final String jdbcname com.mysql.jdbc.Driver; // 连接 URLtest 是数据库名useUnicode 和 characterEncoding 防乱码 private static final String url jdbc:mysql://localhost:3306/test?useUnicodetruecharacterEncodingutf8; // 数据库用户名默认 root private static final String userName root; // 数据库密码改成你安装 MySQL 时设置的密码 private static final String password your_password; // 获取数据库连接 public Connection getCon() throws Exception { Class.forName(jdbcname); // 加载驱动 Connection con DriverManager.getConnection(url, userName, password); return con; } // 关闭连接 public void closeCon(Connection con) { if (con ! null) { try { con.close(); } catch (Exception e) { e.printStackTrace(); } } } }Class.forName(jdbcname)这行是 JDBC 4.0 之前的写法作用是触发驱动类的静态初始化块把驱动注册到 DriverManager 里。MySQL 5.1.8 的驱动类名是com.mysql.jdbc.Driver到了 MySQL 8.0 驱动类名变成了com.mysql.cj.jdbc.DriverURL 也要加时区参数这是版本升级时最容易翻车的地方。useUnicodetruecharacterEncodingutf8这两个参数不加插入中文姓名时数据库里会显示成问号。2.3 增删改查的 DAO 层实现报告里的StudentDao类把增删改查都封装成了独立方法我按它的结构补全关键实现import java.sql.*; import java.util.Vector; public class StudentDao { // 插入学生记录 public int AddStudent(Connection con, Student student) throws Exception { String sql INSERT INTO t_student(name, sex, age, telephone, stdesc, classid) VALUES(?,?,?,?,?,?); PreparedStatement pst con.prepareStatement(sql); pst.setString(1, student.getName()); pst.setString(2, student.getSex()); pst.setInt(3, student.getAge()); pst.setString(4, student.getTelephone()); pst.setString(5, student.getStdesc()); pst.setInt(6, student.getClassid()); return pst.executeUpdate(); // 返回受影响行数 } // 条件查询学生记录 public ResultSet SearchStudent(Connection con, Student student, ModelClass modelclass) throws Exception { StringBuffer sb new StringBuffer(SELECT * FROM t_student ts, t_class tc WHERE ts.classid tc.id); if (student.getName() ! null !student.getName().isEmpty()) { sb.append( AND ts.name LIKE % student.getName() %); } if (student.getClassid() ! -1) { sb.append( AND ts.classid student.getClassid()); } if (modelclass.getProjectid() ! -1) { sb.append( AND tc.projectid modelclass.getProjectid()); } PreparedStatement pst con.prepareStatement(sb.toString()); return pst.executeQuery(); } // 删除学生记录 public int DeleteStudent(Connection con, Student student) throws Exception { String sql DELETE FROM t_student WHERE id ?; PreparedStatement pst con.prepareStatement(sql); pst.setInt(1, student.getId()); return pst.executeUpdate(); } // 修改学生记录 public int ModifyStudent(Connection con, Student student) throws Exception { String sql UPDATE t_student SET sex?, age?, telephone?, classid?, stdesc? WHERE id?; PreparedStatement pst con.prepareStatement(sql); pst.setString(1, student.getSex()); pst.setInt(2, student.getAge()); pst.setString(3, student.getTelephone()); pst.setInt(4, student.getClassid()); pst.setString(5, student.getStdesc()); pst.setInt(6, student.getId()); return pst.executeUpdate(); } }PreparedStatement相比Statement的优势在于预编译和参数绑定能防止 SQL 注入尤其是姓名这种用户输入的字段。executeUpdate()返回的是受影响行数插入成功返回 1更新时如果没匹配到记录返回 0删除同理。查询用executeQuery()返回ResultSet遍历时用rs.next()逐行读取再通过rs.getString(name)或rs.getInt(age)按列名取值。查询方法里拼接 SQL 字符串的做法虽然能跑但存在注入风险更稳妥的方式是全部用?占位符。不过报告里为了演示动态条件拼接用了StringBuffer拼串实际项目里建议改成PreparedStatement加条件判断的方式。3. Swing 界面与模块联动菜单怎么挂、窗口怎么弹3.1 主界面框架与 JDesktopPane 的使用这套系统的界面用 Java Swing 搭建主窗口mainFrm继承JFrame内部用一个JDesktopPane作为容器所有子窗口添加学生、管理学生、添加班级等都以JInternalFrame的形式嵌在里面。菜单栏用JMenuBar挂载五个JMenu每个菜单下挂JMenuItem点击菜单项时通过ActionListener触发对应窗口的创建和显示。主界面的关键代码结构如下import javax.swing.*; public class mainFrm extends JFrame { private JDesktopPane jdesk; // 内部窗口容器 public mainFrm() { initComponents(); this.setExtendedState(JFrame.MAXIMIZED_BOTH); // 启动时最大化 } private void initComponents() { jdesk new JDesktopPane(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 菜单栏 JMenuBar jMenuBar1 new JMenuBar(); JMenu jMenu1 new JMenu(学生管理); JMenuItem jMenuItem1 new JMenuItem(添加学生); JMenuItem jMenuItem2 new JMenuItem(管理学生); // 添加学生菜单项的事件 jMenuItem1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { AddstInter addStInterFrm new AddstInter(); addStInterFrm.setVisible(true); jdesk.add(addStInterFrm); // 把子窗口加入桌面面板 } }); jMenu1.add(jMenuItem1); jMenu1.add(jMenuItem2); jMenuBar1.add(jMenu1); setJMenuBar(jMenuBar1); // 布局 getContentPane().add(jdesk); pack(); } public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new mainFrm().setVisible(true); } }); } }JDesktopPane是 Swing 里做 MDI多文档界面的标准容器子窗口用JInternalFrame而不是JFrame这样它们只能在自己的桌面面板里拖动和缩放不会跑到操作系统任务栏里去。setExtendedState(JFrame.MAXIMIZED_BOTH)让主窗口启动时铺满屏幕适合管理类系统。EventQueue.invokeLater是 Swing 线程模型的规范写法所有界面创建都放在事件调度线程里执行避免界面卡死或组件状态不一致。3.2 登录注册模块与主界面跳转报告里提到了新用户注册和登录页面登录成功后跳转到主界面。登录模块的典型实现是一个LogonFrm继承JFrame包含用户名和密码输入框、登录按钮和注册按钮。点击登录时用 JDBC 查询用户表匹配成功则dispose()当前窗口new mainFrm().setVisible(true)打开主界面。// 登录按钮的事件处理 private void loginBtnActionPerformed(java.awt.event.ActionEvent evt) { String username usernameField.getText(); String password new String(passwordField.getPassword()); DataCon db new DataCon(); try { Connection con db.getCon(); String sql SELECT * FROM t_user WHERE username? AND password?; PreparedStatement pst con.prepareStatement(sql); pst.setString(1, username); pst.setString(2, password); ResultSet rs pst.executeQuery(); if (rs.next()) { this.dispose(); // 关闭登录窗口 new mainFrm().setVisible(true); // 打开主界面 } else { JOptionPane.showMessageDialog(null, 用户名或密码错误); } db.closeCon(con); } catch (Exception e) { e.printStackTrace(); } }passwordField.getPassword()返回的是char[]而不是String这是 Swing 出于安全考虑的设计避免密码在内存里以字符串形式长期驻留。实际项目里密码不应该明文存数据库至少要做一次 MD5 或 SHA 哈希但课程设计阶段为了演示方便很多报告里直接存了明文这一点在答辩时容易被问到。3.3 模块之间的数据传递与刷新添加学生窗口AddstInter和管理学生窗口Manager之间需要数据联动。添加成功后管理窗口的表格要能刷新出新记录。报告里的做法是在插入完成后调用queryAllProcess()重新查询全量数据并更新JTable。JTable的数据模型用Vector嵌套Vector的方式构建外层Vector存行内层Vector存列。// 查询全部学生并刷新表格 public void queryAllProcess() { DataCon db new DataCon(); try { Connection con db.getCon(); String sql SELECT ts.id, ts.name, ts.sex, ts.age, ts.telephone, tc.classname FROM t_student ts LEFT JOIN t_class tc ON ts.classid tc.id; PreparedStatement pst con.prepareStatement(sql); ResultSet rs pst.executeQuery(); VectorString columnNames new Vector(); columnNames.add(ID); columnNames.add(姓名); columnNames.add(性别); columnNames.add(年龄); columnNames.add(电话); columnNames.add(班级); VectorVectorObject data new Vector(); while (rs.next()) { VectorObject row new Vector(); row.add(rs.getInt(id)); row.add(rs.getString(name)); row.add(rs.getString(sex)); row.add(rs.getInt(age)); row.add(rs.getString(telephone)); row.add(rs.getString(classname)); data.add(row); } // 用新数据重建表格模型 studentJTable.setModel(new javax.swing.table.DefaultTableModel(data, columnNames)); db.closeCon(con); } catch (Exception e) { e.printStackTrace(); } }这里用LEFT JOIN而不是INNER JOIN是为了让没有分配班级的学生也能显示出来班级列显示为空。如果用INNER JOINclassid为 null 的学生会直接从结果里消失这是查询模块最容易踩的坑之一。DefaultTableModel的构造函数接收两个Vector第一个是数据行第二个是列名重建模型后JTable会自动刷新显示。4. 环境配置与运行排查那些让你卡半天的坑4.1 驱动版本与 MySQL 版本不匹配现象项目编译通过运行时报java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/test。原因mysql-connector-java-5.1.8-bin.jar没有正确导入到项目的 Build Path 里或者导入的 jar 包版本和 MySQL 服务器版本不兼容。MySQL 8.0 的服务器用 5.1.x 的驱动连会报认证插件不支持的错。解决确认 Eclipse 的 Java Build Path → Libraries 里有这个 jar 包并且 Order and Export 里勾选了。如果 MySQL 是 8.0 版本换成mysql-connector-java-8.0.x的驱动驱动类名改成com.mysql.cj.jdbc.DriverURL 加上?serverTimezoneAsia/Shanghai。4.2 中文乱码问题现象插入学生姓名“张三”在 Navicat 里查出来是“???”或者乱码。原因数据库、表、连接三处的字符集不一致。MySQL 5.5 默认的character_set_server可能是latin1而 Java 端用 UTF-8 发送数据。解决建库时指定CREATE DATABASE test DEFAULT CHARSET utf8 COLLATE utf8_general_ci;建表时也加DEFAULT CHARSETutf8JDBC URL 里加useUnicodetruecharacterEncodingutf8。三处统一成 utf8 后乱码问题基本消失。4.3 外键约束导致插入失败现象添加学生时选了班级但插入报Cannot add or update a child row: a foreign key constraint fails。原因学生表里的classid引用了班级表的id但传入的classid在班级表里不存在。常见于班级下拉框数据没加载完就点了保存或者班级被删除后学生记录还挂着旧 ID。解决插入前先查一次班级表确认classid有效或者在界面上把班级下拉框做成必选项且数据从数据库实时加载。如果业务允许学生暂时没有班级把classid设为允许 NULL外键约束就不会拦截。4.4 JTable 数据不刷新现象插入或删除学生后表格里的数据没变要关掉窗口重新打开才更新。原因JTable的数据模型没有重新设置或者用了fireTableDataChanged()但底层Vector没变。解决每次增删改操作后重新查询数据库并调用setModel()重建表格模型这是最稳妥的做法。如果数据量小也可以直接操作DefaultTableModel的addRow()和removeRow()方法再调用fireTableDataChanged()通知界面刷新。4.5 窗口关闭后程序不退出现象关掉主窗口后Java 进程还在后台跑Eclipse 的控制台红点没灭。原因JFrame的默认关闭操作是HIDE_ON_CLOSE只隐藏窗口不结束进程。如果有非守护线程还在运行进程就不会退出。解决主窗口设置setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)确保关闭时调用System.exit(0)。如果用了JDesktopPane里的子窗口子窗口的默认关闭操作设为DISPOSE_ON_CLOSE只销毁自己不影响主窗口。5. 进阶技巧用 PreparedStatement 重写查询与事务控制5.1 把动态拼接 SQL 改成参数化查询报告里的SearchStudent方法用StringBuffer拼 SQL姓名直接拼进LIKE子句存在注入风险。我一般会改成PreparedStatement加条件列表的方式public ResultSet SearchStudentSafe(Connection con, Student student, ModelClass modelclass) throws Exception { StringBuilder sql new StringBuilder( SELECT ts.*, tc.classname FROM t_student ts LEFT JOIN t_class tc ON ts.classid tc.id WHERE 11); java.util.ListObject params new java.util.ArrayList(); if (student.getName() ! null !student.getName().isEmpty()) { sql.append( AND ts.name LIKE ?); params.add(% student.getName() %); } if (student.getClassid() ! -1) { sql.append( AND ts.classid ?); params.add(student.getClassid()); } if (modelclass.getProjectid() ! -1) { sql.append( AND tc.projectid ?); params.add(modelclass.getProjectid()); } PreparedStatement pst con.prepareStatement(sql.toString()); for (int i 0; i params.size(); i) { pst.setObject(i 1, params.get(i)); } return pst.executeQuery(); }WHERE 11是个常用技巧让后续的AND条件可以直接追加不用判断是不是第一个条件。参数用ListObject收集最后统一setObject绑定顺序和占位符一一对应。这样改完姓名里带单引号也不会把 SQL 搞崩。5.2 用事务保证学籍变更和奖惩记录的一致性学籍变更、奖励、处罚这三类操作经常需要同时写多张表。比如转系时既要更新学生表的classid又要往学籍变更表插一条记录。如果第一条成功第二条失败数据就不一致了。这时候需要手动控制事务public boolean changeClass(Connection con, int studentId, int newClassId, String remark) { try { con.setAutoCommit(false); // 关闭自动提交开启事务 // 第一步更新学生表的班级 String sql1 UPDATE t_student SET classid? WHERE id?; PreparedStatement pst1 con.prepareStatement(sql1); pst1.setInt(1, newClassId); pst1.setInt(2, studentId); pst1.executeUpdate(); // 第二步插入学籍变更记录 String sql2 INSERT INTO t_status_change(studentid, change_type, change_date, remark) VALUES(?,?,?,?); PreparedStatement pst2 con.prepareStatement(sql2); pst2.setInt(1, studentId); pst2.setString(2, 转系); pst2.setDate(3, new java.sql.Date(System.currentTimeMillis())); pst2.setString(4, remark); pst2.executeUpdate(); con.commit(); // 两步都成功提交事务 return true; } catch (Exception e) { try { con.rollback(); // 任何一步失败回滚 } catch (Exception ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { try { con.setAutoCommit(true); // 恢复自动提交 } catch (Exception e) { e.printStackTrace(); } } }setAutoCommit(false)之后所有 SQL 操作都在同一个事务里直到commit()才真正写入。中间任何一步抛异常rollback()会把之前的所有操作撤销。finally块里恢复setAutoCommit(true)是为了让这个连接后续还能被其他方法正常使用不然连接会一直挂着未提交的事务。5.3 验证数据库操作是否生效的几种方式写完代码别急着点界面按钮先用几种方式确认数据库层面真的动了。第一种直接在 Navicat 里右键表 → 打开表看数据有没有变化。第二种用命令行SELECT COUNT(*) FROM t_student;对比操作前后的行数。第三种在 Java 代码里executeUpdate()的返回值打印出来插入成功返回 1更新时如果返回 0 说明WHERE条件没匹配到任何记录。第四种开启 MySQL 的通用查询日志在my.ini里加general_log1和general_log_filemysql_general.log所有执行的 SQL 都会记下来排查时一目了然。我自己的习惯是每次改完 DAO 层的方法先在main方法里写个临时测试跑一遍增删改查确认 SQL 没问题了再接界面。界面层的问题和数据库层的问题混在一起排查效率会低很多。从那以后我每次做这种 CRUD 项目都强制先把数据库层单独跑通再动 Swing省下来的调试时间够我把表结构再优化两轮。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?