ARTICLE DETAIL

资讯详情

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

Java+MySQL学生信息管理系统:JDBC三层架构与Swing实战解析

Java+MySQL学生信息管理系统:JDBC三层架构与Swing实战解析 简介围绕JavaMySQL实现学生信息管理系统这份PDF资料定位为课程设计辅导与Java入门学习材料适合初学Java Swing与JDBC的开发者、在校学生及需要完成类似课设的人群。资源共1个文件以PDF文档封装压缩包仅135KB内容精简但信息密度较高覆盖开发环境部署JDK7MySQL5、model-dao-view三层项目结构、数据库建库建表语句以及管理员和学生实体类核心定义能帮助读者快速搭建项目骨架并理清增删改查流程。文档中还给出了GitHub源码仓库地址方便有需要的读者获取完整工程。目前已有5010人浏览学习这份轻量级参考文档适合在动手编码前先快速通读再对照自己的项目扩展改造。1. JavaMySQL实现学生信息管理系统课设能跑但别只会抄如果你正在找 JavaMySQL 实现学生信息管理系统的源码大概率是期末课设或毕业设计卡住了。这套基于 Java Swing MySQL 的小系统确实能跑模型分层也清晰model-dao-view 三层结构对理解 JDBC 增删改查非常友好。但直接拿它交作业的人多了导师一眼就能看出来。更值钱的是把它的设计逻辑拆开——为什么用单例封装 DBUtil、为什么要用 PreparedStatement、分页为什么写成 limit ?,?、查重为什么放在 DAO 层。这套源码适合刚学完 JavaSE 和 MySQL、想看看真实课设长什么样的同学也适合需要快速改造成自己项目的上班族。我会把建库、封装的代码、跑通步骤和四个最常见的坑一次讲透让你不只会复制还会改。2. 系统结构model-dao-view 三层先看懂再动手2.1 从建库脚本开始admin 表和 student 表的设计意图拿到源码第一件事不是打开 IDE而是看 SQL 脚本。项目根目录下的建库语句就是整套系统的数据契约。只有两张表admin 和 student。admin 表存登录账号只有三个字段——id、name、username、password。注意初始化数据只有一行INSERT INTO admin VALUES (1,admin,admin,admin)也就是系统默认管理员账号 admin密码 admin。这个账号写在代码里没有加盐、没有 MD5属于课设级别但对理解登录流程已经够用了。student 表是核心数据表字段有 id、name、sno、department、hometown、mark、email、tel、sex。它没有设置唯一索引但业务上 sno学号应该是唯一的。源码里通过 DAO 层的queryBySno方法在新增前做查重这一点很聪明——数据库层面不加约束把规则放到业务层虽然不够严谨但能让代码演示“如何手动校验重复”。字段类型几乎全是 varchar(20)包括电话和邮箱这在实际生产中是反模式但在课设场景里是为了避免数据类型转换报错减少新手踩坑。建表语句里有一个细节AUTO_INCREMENT22和AUTO_INCREMENT2。这说明作者之前已经插入过测试数据重置自增起始值是为了让演示数据的 id 从 18 开始。你导入时如果报错多半是本地已有同名表。执行脚本前建议先DROP TABLE IF EXISTS脚本里已经带了但如果你手动分段执行容易忽略这一点。执行整份 SQL 脚本即可默认会创建 student 数据库和两张表。如果你用的 MySQL 8.0注意默认字符集已经是 utf8mb4而脚本里写的是DEFAULT CHARSETutf8不影响但如果你建表后插入中文出现乱码需要确认连接 URL 里是否指定了characterEncodingutf8后面避坑章节细说。2.2 DBUtil 封装的三个关键点单例、PreparedStatement、资源关闭看代码先看com.student.util.DBUtil。这个工具类只有五六十行但浓缩了 JDBC 编程的三个关键决策。第一是单例模式private static DBUtil db;加public static DBUtil getDBUtil()确保整个 JVM 里只有一个数据库工具类实例。这对于 Swing 桌面应用问题不大但它埋了一个隐患——conn、ps、rs都是成员变量多线程环境下会串。课设是单线程界面操作所以没事。如果你要改成多线程批量导入必须把这三个变量改成方法局部变量否则会出现“拿着别人的连接去查数据”的玄学问题。第二是全部使用 PreparedStatement。看executeUpdate(String sql, Object[] obj)和executeQuery(String sql, Object[] obj)参数用ps.setObject(i 1, obj[i])绑定杜绝了字符串拼接 SQL 的注入风险。这是值得背下来的写法。比如登录查询String sql select * from admin where username? and password?; String[] param { username, password }; rs db.executeQuery(sql, param);这里没有用select * from admin where username username 这种写法。PreparedStatement 先把 SQL 语法预编译再把参数当纯文本传入即使 username 是 or 11 --也不会改变 SQL 结构。从课设开始养成这个习惯后期接触 MyBatis 时也会更容易理解#{}和${}的区别。第三是资源关闭。getConn()方法里判断conn null || conn.isClosed()避免每次查询都重新建立连接。close()方法统一关 rs、ps、conn。但注意一个 bugexecuteQuery(String sql)方法没有调用close()因为它要把 ResultSet 返回给调用方调用方在 DAO 里用完后再调destroy()。如果调用方忘记关闭连接就会泄漏。源码里所有 DAO 方法都在 finally 里调用了destroy()这个习惯是对的但你要清楚destroy()只关了 rs最后靠db.close()把 conn 也关了。这是典型的“手动管理连接”模式数据量小没问题数据量大时会频繁开关连接性能瓶颈明显。2.3 BaseDAO 与 DAO 实现登录校验、增删改查怎么串起来BaseDAO 是一个抽象类持有一个db字段子类共用同一个 DBUtil 实例。它还有一个静态的getAbilityDAO(DAO dao)工厂方法用枚举 DAO 区分 AdminDAO 和 StudentDAO返回单例。这种写法在小型项目里有点过度设计但好处是换实现类时不用改调用方代码。如果你要扩展一个 TeacherDAO只需要加一个枚举值再在 switch 里增加分支。AdminDAO 只有一个queryForLogin方法验证逻辑很简单先查长度是否为 0再用select * from admin where username? and password?查询只要rs.next()为 true 就算登录成功。注意它没有检查账号不存在的情况只是返回 boolean。实际课设的 LoginView 拿到这个 boolean 后跳转 MainView。这里有一个细节密码校验放在 SQL 里做意味着数据库表里如果存的是加密后的密文这个写法就得改。你以后做项目时密码字段只会越做越复杂但登录流程本质不变查询 → 判断结果集是否有值 → 返回状态。StudentDAO 是核心包含 add、delete、update、queryByName、list 五个方法。add 和 update 之前都会先调queryBySno做查重。queryBySno是 private 方法只在本类内部使用它返回 1 表示已存在0 表示不存在。add 里如果queryBySno(stu.getSno()) 1就 return false防止重复学号。update 里如果 0也 return false防止更新一个不存在的学生。这是业务规则也是课设里最容易重复造轮子的地方。list 方法实现分页写死了每页 15 条SQL 是select * from student limit ?,?用Integer[] param { beginNum, showNum }。注意 limit 第一个参数是偏移量(pageNum - 1) * showNum是手动算出来的。这种分页在数据量小的时候没问题但 limit 的偏移量越大查询越慢MySQL 里深分页是经典性能问题。如果你把项目改成真正的系统需要换用“带条件的分页查询”或者“基于游标的分页”。后面进阶章节我再展开。queryByName 用的是like ?参数是% name %这样支持模糊查询。它返回 String[][] 二维数组因为 JTable 的 DefaultTableModel 可以直接用二维数组填充视图。这个设计虽然丑但胜在直接不用额外做对象到数组的转换。整个 DAO 层的调用关系是View 层按钮事件 → 调用 StudentDAO 单例 → 方法内部用 DBUtil 执行 SQL → 返回二维数组或 boolean → View 层刷新表格。理解这条链路你就能在任何一个环节加日志排查问题。3. 把项目跑起来JDK7MySQL5 环境下的完整复现步骤3.1 环境准备与数据库初始化源码作者标注的开发环境是 jdk7 MySQL5 win7但这不代表你非要用古董版本。我实际在 JDK8、JDK11 上跑过MySQL 5.7 和 8.0 也都兼容只要把 JDBC 驱动换成对应版本就行。项目依赖的 MySQL Connector/J 驱动是经典的com.mysql.jdbc.Driver在 MySQL 8.0 里这个类已经改名成com.mysql.cj.jdbc.Driver所以如果你的AppConstants里写的是老驱动类名直接跑 8.0 会报 ClassNotFoundException。这个坑我在避坑章节单独说。先说数据库准备。打开 MySQL 命令行用 root 执行项目自带的 SQL 脚本mysql -u root -p student.sql如果脚本文件不在当前目录给绝对路径。执行完用show tables确认有 admin 和 student 两张表mysql -u root -p USE student; SHOW TABLES; SELECT * FROM admin; SELECT * FROM student;看到三行学生数据张三、李四、王五就说明导入成功。这里要注意如果你的 MySQL 是 5.7 或 8.0默认 sql_mode 里可能有ONLY_FULL_GROUP_BY但这次只做增删改查不涉及聚合查询不会踩到。如果执行过程中报“Unknown collation”之类的错大概率是脚本里的表结构和你 MySQL 默认字符集冲突我在第 4 章给解决方案。3.2 导入项目到 IDE 并修改 AppConstants源码上传在 GitHub你 clone 或下载 zip 后得到的是一个 Eclipse 工程。如果你用 IDEA更方便直接File - New - Project from Existing Sources选项目根目录然后一路 Next。IDEA 默认会识别 src 下的包结构。如果你的 IDEA 版本较新可能提示“Project SDK 未配置”选一个已安装的 JDK 8 就好。不要选太新的 JDK 17Swing 能跑但源码里有些老写法可能触发编译警告不影响运行。接下来关键一步找到com.student.AppConstants这个常量类。它藏着 JDBC 连接信息包括驱动类名、URL、用户名、密码。不同 MySQL 版本需要微调。我贴一段典型配置package com.student; public class AppConstants { public static final String JDBC_DRIVER com.mysql.jdbc.Driver; public static final String JDBC_URL jdbc:mysql://localhost:3306/student?characterEncodingutf8; public static final String JDBC_USERNAME root; public static final String JDBC_PASSWORD 123456; }JDBC_URL里我加上了characterEncodingutf8否则插入中文容易变问号。如果你用的是 MySQL 8.0驱动类名要改成public static final String JDBC_DRIVER com.mysql.cj.jdbc.Driver;并且 URL 里最好补上serverTimezoneAsia/Shanghai否则高版本 MySQL 驱动会报时区错误。修改后记得保存然后重新编译。这个类里还定义了很多界面显示用的字符串常量比如MAINVIEW_TITLE、STUDENT_NAME等你可以进源码看看都在同一个文件里。3.3 主界面 MainView 的分页与查询逻辑MainView 是系统主窗体代码量最大。它用 JTable 展示数据顶部一个文本框输入查询条件点击查询按钮或回车触发FindListener。底部一排按钮控制分页首页、上一页、下一页、末页。还有一个新增、删除、修改按钮。这个界面是课设标准配置但它的实现方式有可学习的地方。看分页逻辑private final int maxPageNum 99; public static int currPageNum 1;当前页码是静态变量意味着所有窗口共享这个状态。jButtonNext的响应里大概会有currPageNum再调用StudentDAO.getInstance().list(currPageNum)。数据刷新通过DefaultTableModel.setDataVector(result, column)完成。这种硬编码分页上限 99 是一种防呆设计避免用户一直点下一页把偏移量撑爆。但更好的做法是根据总记录数计算最大页码源码里没做因为 student 表总共才 3 条数据分不分页无所谓。查询按钮的逻辑是拿到文本框输入的姓名调用queryByName返回二维数组后同样 setDataVector。注意queryByName用的是like ?所以支持模糊查询比如输入“张”能查出张三。如果你在查询后马上做新增或删除再点查询结果还是旧数据因为queryByName每次都会重新查库所以不会有缓存问题。真正需要小心的是 JTable 的刷新时机你做完新增后必须手动调用一次list(currPageNum)或queryByName否则表格不会自己更新。这是 Swing 新手最常见的困惑。运行入口在哪源码没有 main 方法在 MainView 里吗实际上入口在 LoginView 或者一个单独的启动类。你需要在 LoginView 里找到 main 方法右键运行。如果没有 main可能在 AppConstants 附近或者某个 Test 类里。跑起来后输入 admin/admin 就能进入主界面。如果登录后表格是空的先检查数据库里 student 表是否有数据。4. 避坑与常见问题MySQL 版本、驱动、乱码、连接泄漏4.1 现象ClassNotFoundException: com.mysql.jdbc.Driver原因你用了 MySQL 8.0 的驱动包或者根本没把 mysql-connector-java.jar 加入项目依赖。 JDBC 4 驱动支持自动注册但源码里显式写了Class.forName(AppConstants.JDBC_DRIVER)类名不对就凉。解决到 Maven 仓库或 MySQL 官网下载mysql-connector-java-8.0.x.jar放入项目 lib 目录并加入 Build Path。同时把AppConstants.JDBC_DRIVER改成com.mysql.cj.jdbc.Driver。如果用的是 5.1.4x 版本的驱动包老类名仍然存在可以不改但建议改成新类名以匹配 MySQL 8.0。4.2 现象插入或显示中文变问号原因有两层第一层是数据库表字符集不是 utf8或者连接 URL 没带characterEncoding参数。第二层是 JTable 渲染默认字体不支持中文但这在 Windows 下少见。解决先检查 SQL 脚本开头有没有DEFAULT CHARSETutf8没有就ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4;。然后在JDBC_URL末尾加?characterEncodingutf8。我见过更隐蔽的坑Windows 控制台默认 GBK你硬把 JDBC 参数改成 utf8 反而乱码。正确做法是保证数据库表是 utf8连接 URL 用useUnicodetruecharacterEncodingutf8界面代码里用汉字字面量而不是从控制台读输入。这个项目里所有中文都是硬编码在代码里的所以只要表和连接正常界面就不会乱码。4.3 现象登录成功但主界面查不到学生数据原因student 表是空的或者你连接的数据库不是脚本创建的 student 库。常见的低级错误是 IDE 里配置了 MySQL 连接但是连的是系统自带的 test 库而代码里JDBC_URL写的是jdbc:mysql://localhost:3306/student两个库当然不同。解决先在 MySQL 命令行执行USE student; SELECT * FROM student;确认三行数据存在。再在 IDE 的 Database 面板检查连接 URL 的库名。如果数据存在但 JTable 还是空看看list(int pageNum)方法的 SQLselect * from student limit ?,?如果pageNum传了 0beginNum就是负值MySQL 会报错但被 DAO 吞掉了返回 null。所以按钮事件里要保证当前页从 1 开始。4.4 现象新增、删除、修改后表格内容不变原因DAO 操作成功只是写进了数据库但 JTable 的数据模型没有刷新。Swing 不会自动感知后端数据变化必须手动重新查询。解决在每个操作按钮的事件回调里做完 add/delete/update 之后加上一行刷新代码String[][] data StudentDAO.getInstance().list(currPageNum); DefaultTableModel model (DefaultTableModel) MainView.jTable.getModel(); model.setDataVector(data, MainView.column);注意currPageNum是静态变量如果删除的是当前页最后一条数据页码可能越界你可以在刷新前判断一下当前页是否还有数据没有就currPageNum--。这个细节是课设评分时老师爱问的点答上来能加分。4.5 现象执行 SQL 脚本时提示 ERROR 1064 或语法错误原因你用的 MySQL 8.0 对某些关键字更敏感或者脚本里的LOCK TABLES写法在严格模式下被拒。源码脚本里有LOCK TABLES ... WRITE和UNLOCK TABLES这本身没问题但如果你在导入时和别的进程同时操作表可能锁等待超时。解决把脚本中的 LOCK TABLES / UNLOCK TABLES 段去掉只保留 CREATE TABLE 和 INSERT 语句。另外MySQL 8.0 的默认字符集是 utf8mb4脚本里写utf8也能兼容但如果你导入后看到表里全乱码用上面 4.2 的转换语句处理。4.6 现象运行后日志一堆 SQLException但程序没退出原因DBUtil 中executeUpdate(String sql, Object[] obj)在 catch 里打 e.printStackTrace 后程序继续执行可能返回 -1 但界面没提示。这种静默失败非常讨厌尤其是密码错误时登录窗口什么反应都没有。解决在 DAO 层的方法里打印返回值或抛出自定义异常。你可以临时在queryForLogin里加一行System.out.println(login result result);来定位。注意源码的executeUpdate在成功时会调用close()而executeQuery不关这两种方法的使用场景别搞混。如果你在queryForLogin里手动调用close()了那后面rs.next()会直接报“ResultSet closed”的错。我见过有人把 close 写进executeQuery里然后整个系统所有查询都返回空折腾半天。5. 进阶改造分页优化、连接池替代 DriverManager、验证习惯5.1 把硬编码分页改成带总记录数的可靠分页源码的list(int pageNum)写死了每页 15 条页数上限 99。这在演示数据下没问题但如果你导入了上千条学生记录这种写法有两个隐患一是 limit 偏移量大会导致查询越来越慢二是末页按钮和总页数完全靠猜。我一般会改成先查总条数再查当页数据public int getCount() { String sql select count(*) from student; rs db.executeQuery(sql); try { if (rs.next()) { return rs.getInt(1); } } catch (SQLException e) { e.printStackTrace(); } finally { destroy(); } return 0; } public String[][] listByPage(int pageNum, int pageSize) { int beginNum (pageNum - 1) * pageSize; String sql select * from student limit ?,?; Integer[] param { beginNum, pageSize }; rs db.executeQuery(sql, param); // 后续的 buildList 和 buildResult 相同 }改造后MainView 的首页、末页按钮可以用getCount()除以每页条数来计算真实最大页而不是写死 99。这个改造能体现你对 MySQL 分页的理解课设答辩时是加分项。注意getCount()里的destroy()会关掉 ResultSet所以必须在读取完 count 之后调用顺序不能反。5.2 用连接池替代 DriverManager 单例源码的 DBUtil 是手动管理连接每次查询最多复用一次连接然后在 DAO 的 finally 里关闭。数据量小无所谓但如果你要同时执行多个查询单连接串行会卡界面。常见做法是换成 Druid 或 HikariCP。对课设来说引入一个连接池可能太重但你可以在代码层面做最小改造保持 DBUtil 的接口不变内部把getConn()改成从一个简易池里取连接。比如用一个 LinkedList 保存空闲连接private static LinkedListConnection pool new LinkedList(); private Connection getConn() throws SQLException { if (pool.isEmpty()) { Class.forName(AppConstants.JDBC_DRIVER); return DriverManager.getConnection( AppConstants.JDBC_URL, AppConstants.JDBC_USERNAME, AppConstants.JDBC_PASSWORD); } return pool.removeFirst(); } public void close() { // 不真正关闭放回池里 pool.addLast(conn); }这种简易池不处理连接失效但足够应付课设演示。你要是想更专业一点直接引入 HikariCP把 DBUtil 里的getConn()替换成dataSource.getConnection()。关键是先跑通现有逻辑再替换底层别一上来就重构否则会陷入“改完连接池整个界面不刷新”的新坑。5.3 最终验证把增删改查全部走一遍改造完成后我建议你在交作业前强制走一遍完整流程重置数据库到初始脚本状态启动程序用 admin/admin 登录。新增一个学生学号填一个不存在的确认表格多出一行。再新增一次重复学号确认提示失败。修改这个学生的院系和电话确认表格更新。按姓名模糊查询确认能查到。删除刚新增的学生确认表格减少。点击下一页再回首页确认分页正常。最后关掉程序重新打开检查数据是否持久化。这一套走下来基本覆盖了所有评分点。如果哪一步没通过优先看控制台 SQLException 堆栈再回到对应的 DAO 方法里打断点。从那以后我每次拿到课设源码都会先跑一遍原始脚本再用 5.3 的清单过一遍功能最后才动手改代码。这样做的好处是能区分“源码本身的问题”和“环境引入的问题”省得改到一半发现是驱动版本不对白忙一场。希望这个学生信息管理系统能成为你理解 JDBC 和 Swing 协作的活教材而不是一个复制粘贴就完事的黑匣子。祝你顺利跑通也能把它改造成自己的作品。本文还有配套的精品资源点击获取
返回列表