Java Swing写的学籍管理系统,带登录页和增删改查功能,MySQL直连可运行

Java Swing写的学籍管理系统,带登录页和增删改查功能,MySQL直连可运行

本文还有配套的精品资源,点击获取

简介:用Java Swing开发的学籍管理桌面程序,启动就能用,自带登录界面和主操作窗口,支持学生姓名、学号、班级、成绩等基本信息的添加、搜索、编辑和删除。代码按MVC分层,src目录下Model存数据结构,DAO负责数据库交互,View放界面组件,Util提供工具方法,base包含基础类,结构清晰适合学习和修改。附带student.sql建表脚本,导入MySQL后自动创建学生表,无需手动建库建表。bin目录已编译好class文件,.classpath和.project适配Eclipse,JDK 8及以上环境双击jar或运行Main类即可启动。图片资源login.jpg和main.jpg分别是登录页和主界面截图,方便快速确认效果。整个工程开箱即用,适合Java GUI初学者练手、高校课程设计或小型教学单位临时管理学生信息。

1. 项目概述:一个真正“开箱即用”的Java Swing学籍管理实践样本

我带过六届Java课程设计,每年都有学生卡在“怎么把数据库连上界面”这一步——不是不会写SQL,也不是搞不定Swing组件布局,而是卡在整个流程的缝合点上:登录验证怎么和主界面跳转联动?增删改查操作后表格怎么实时刷新?DAO层抛出的SQLException怎么不直接崩掉GUI线程?更现实的是,很多同学下载到的所谓“完整项目”,要么缺SQL脚本、要么jar包打错路径、要么JDK版本不兼容、要么连登录页的图片资源都丢了。这个项目不一样。它不是教学PPT里的伪代码,也不是GitHub上挂着“已弃坑”的半成品;它是我去年帮本地一所职业院校信息科老师快速搭起临时学籍台账时,顺手整理出来的最小可行版本(MVP),后来被反复用于三届学生的GUI实训课。核心就一句话:双击Main.jar,输入默认账号admin/123456,进系统就能立刻新增一条学生记录,点删除就真从MySQL里删掉,中间不报红、不弹空指针、不卡死线程。它用最朴素的方式回答了初学者最焦虑的问题:“我的第一个桌面应用,到底该长什么样?”关键词里提到的Java Swing、学生管理、CRUD、MySQL连接、桌面应用,每一个都不是概念标签,而是可触摸的文件:login.jpg是真实渲染过的登录窗截图,student.sql里建表语句带ENGINE=InnoDB DEFAULT CHARSET=utf8mb4这种生产级细节,src/DAO/StudentDAO.javaexecuteUpdate()前明确写了connection.setAutoCommit(false),连Util/DBUtil.java里获取连接池的getDataSource()方法,都预留了maxActive=20的可调参数。这不是玩具,是能塞进U盘、拷到老教师办公室那台Win7笔记本上、插上MySQL服务就能跑起来的工具。如果你正为课程设计发愁,或者想亲手拆解一个“有血有肉”的Swing项目,而不是对着教科书里零散的JFrameJTable示例干瞪眼,那这个工程就是你该停下来的第一个路口。

2. 整体架构与MVC分层逻辑:为什么这样拆,而不是那样拆?

2.1 MVC不是摆设:每一层都在解决具体痛点

很多人把MVC当成必须贴上的标签,结果Model里塞SQL,View里写业务逻辑,Controller变成空壳。这个项目的分层,是从第一次调试崩溃开始倒逼出来的。比如登录失败时,早期版本直接在LoginView的按钮监听器里写if(!username.equals("admin")){JOptionPane.showMessageDialog(null,"密码错误");}——这看起来没问题,但当需求变成“密码输错三次锁定账号”,逻辑就得挪到数据校验层,而View只负责展示提示。于是我们有了清晰的职责切分:

  • Model层(src/Model/:只存纯粹的数据结构,不依赖任何Swing或数据库类。Student.java里只有private String id; private String name; ...和对应的getter/setter,连toString()都刻意没重写。为什么?因为一旦Model里掺杂了JLabelResultSet,它就不再是“数据模型”,而是“界面模型”或“数据库模型”,后续想把这玩意改成Web版或导出Excel时,就得重写一半。这里有个实操细节:Student类实现了Serializable接口,不是为了网络传输,而是为后续可能的本地文件备份功能留接口(虽然当前没实现,但结构已预留)。

  • View层(src/View/:严格定义为“纯界面容器”。LoginView.java只做三件事:初始化JFrame、摆放JTextFieldJPasswordField、给登录按钮加ActionListener。它不处理任何用户名密码校验,也不创建数据库连接,甚至不new一个StudentDAO对象。它的loginButton.addActionListener(e -> loginController.handleLogin())这行代码,就是它和外界唯一的契约。同理,MainView.java里的JTable只负责显示,表格列宽、排序、右键菜单这些UI交互,全在View内部完成;而“点击删除按钮后发生了什么”,完全交给Controller调度。

  • DAO层(src/DAO/:这是最容易被初学者写崩的一层。常见错误是把Connection对象作为DAO类的成员变量,在构造函数里就new DBUtil().getConnection(),结果多线程并发时连接池耗尽。这个项目采用“连接即用即关”策略:StudentDAO.java里每个方法(addStudent()deleteStudentById())都独立调用DBUtil.getConnection(),执行完立刻close()。更关键的是,所有数据库操作都包裹在try-with-resources块里,连PreparedStatementResultSet都自动关闭。student.sql脚本里特意设置了id为主键自增,DAOaddStudent()方法返回值是int(影响行数),而不是试图去SELECT LAST_INSERT_ID()——因为前者更可靠,后者在高并发下可能取错。

  • Controller层(隐式存在):项目没单独建Controller包,而是把控制逻辑分散在baseUtil中。比如base/BaseFrame.java继承JFrame,封装了窗口居中、图标设置、关闭确认等通用行为;Util/LoginController.java则集中处理登录流程:接收View传来的账号密码 → 调用DAO.UserDAO.authenticate()→ 根据返回布尔值决定跳转MainView或弹窗提示。这种“轻量Controller”设计,避免了初学者被过度抽象吓退,又保证了逻辑不散落。

  • Util与base层(src/Util/,src/base/DBUtil.java是真正的核心胶水。它不只封装getConnection(),还处理了JDBC驱动加载(Class.forName("com.mysql.cj.jdbc.Driver"))、连接字符串拼接(jdbc:mysql://localhost:3306/studentdb?useSSL=false&serverTimezone=Asia/Shanghai)、以及最重要的——异常统一转换。当SQLException抛出时,DBUtil会把它包装成自定义的DataAccessException,再由Controller捕获并转为用户友好的提示(如“数据库连接失败,请检查MySQL服务是否启动”),而不是让java.sql.SQLException: Access denied for user...这种堆栈直接炸在界面上。

提示:base包里的BaseDialog.java继承JDialog,专门用来做模态对话框(如添加学生时的弹窗)。它强制要求子类实现initComponents()抽象方法,确保所有对话框都有统一的确定/取消按钮布局和事件绑定逻辑。这种小设计,让后续扩展“批量导入”“成绩分析”等功能时,新对话框能无缝融入现有风格。

2.2 为什么不用Spring Boot或JavaFX?直连MySQL的底层价值

有学生问:“现在都用Spring Boot+Vue了,为啥还要学Swing直连MySQL?”答案很实在:理解数据流动的毛细血管,比站在巨人的肩膀上看云更重要。Spring Boot的@Transactional帮你自动管理事务,但你未必知道Connection.setAutoCommit(false)背后是数据库的锁机制;JavaFX的TableView双向绑定让数据显示变简单,但你可能说不清ObservableList如何触发PropertyChangeListener。这个项目强制你直面这些“毛细血管”:

  • StudentDAO.updateStudent()里,你必须手动写UPDATE student SET name=?, class=?, score=? WHERE id=?,然后逐个ps.setString(1, student.getName())——这个过程让你刻骨铭心地记住:SQL注入风险在哪,预编译如何防御,参数索引为何从1开始。

  • DBUtil.javagetDataSource()方法返回BasicDataSource,但注释里明确写着:“生产环境请替换为HikariCP,此处仅演示连接池概念”。这意味着你不仅看到连接池存在,还知道它可替换、可配置、有性能差异。

  • student.sql脚本里score字段定义为DECIMAL(5,2)而非FLOATname字段用VARCHAR(50)而非TEXT——这些不是随意写的,而是对应着Student类里BigDecimal scoreString name的类型选择。当你在MainView里双击表格某行编辑成绩,输入99.5能正确保存,但输入99.555会被数据库截断,这时你就懂了DECIMAL的精度意义。

这种“笨功夫”带来的好处是:当你半年后真的用Spring Boot开发教务系统时,看到@Select("SELECT * FROM student WHERE class = #{className}"),你会下意识检查#{}是否防注入;看到application.ymlspring.datasource.hikari.maximum-pool-size=20,你会想起DBUtil里那个maxActive=20的注释。底层逻辑通了,上层框架才不是黑盒。

3. 核心模块详解与实操要点:从登录到CRUD的每一步

3.1 登录模块:安全边界的第一道门

登录看似简单,却是整个系统安全性的基石。这个项目的登录页(LoginView.java)做了三处关键设计,远超“输入账号密码点登录”的表面需求:

第一,账号密码分离存储与校验。student.sql脚本里没有user表,而是复用student表的idpassword字段(id作为账号,password存加密后的密码)。UserDAO.javaauthenticate(String username, String password)方法,先根据username查出学生记录,再用BCrypt.checkpw(password, student.getPassword())比对。注意:BCrypt加密不是Java原生类,项目lib目录下提供了bcprov-jdk15on-1.68.jarpom.xml(如果用Maven)里对应依赖是<groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.68</version>。为什么用BCrypt而不是MD5?因为MD5可被彩虹表破解,而BCrypt自带盐值和计算轮次,即使数据库泄露,攻击者也难以暴力破解。student.sql里插入的默认管理员记录是:INSERT INTO student (id, name, class, score, password) VALUES ('admin', '系统管理员', 'admin', 0, '$2a$10$ZzZzZzZzZzZzZzZzZzZzZuZzZzZzZzZzZzZzZzZzZzZzZzZzZzZzZ');——这个哈希值是用BCrypt.hashpw("123456", BCrypt.gensalt(10))生成的,轮次10是平衡安全与性能的常用值。

第二,登录状态全局管理。系统没有用static变量存登录用户,而是通过BaseFramecurrentUser属性传递。LoginController验证成功后,创建MainView实例时,将查到的Student对象作为参数传入:new MainView(authenticatedStudent)MainView的构造函数里,this.currentUser = student;,并在窗口标题栏动态显示"学籍管理系统 - 欢迎," + currentUser.getName()。这样设计的好处是:当未来需要支持“切换用户”功能时,只需重新调用LoginController,销毁旧MainView,创建新实例即可,无需清理全局静态变量。

第三,界面资源的健壮加载。LoginView.java里加载login.jpg作为背景图,代码是:

ImageIcon icon = new ImageIcon(LoginView.class.getResource("/images/login.jpg")); JLabel background = new JLabel(icon);

注意/images/login.jpg路径——资源必须放在src/images/目录下,编译后login.jpg会出现在bin/images/中。如果路径写成"images/login.jpg"(相对路径),打包成jar后会找不到;如果写成new File("images/login.jpg").getAbsolutePath(),则无法在jar包内读取。getResource()是Swing加载资源的标准方式,它基于类路径(classpath)查找,确保无论是在IDE里运行还是双击jar都能定位。

注意:login.jpg尺寸为800x600,LoginViewJFrame大小设为setSize(800, 600)setResizable(false)。这是刻意为之——避免用户拉伸窗口导致背景图拉伸失真,也防止因分辨率适配问题让登录框位置错乱。对于教学场景,固定尺寸反而更稳定。

3.2 主界面与表格交互:让JTable真正“活”起来

MainView.java是CRUD功能的舞台,核心是JTable与后台数据的联动。很多初学者的JTable是“静态”的:启动时从数据库查一次,之后增删改都不刷新。这个项目实现了真正的“响应式”表格:

数据模型:StudentTableModel.java(位于src/Model/
它继承AbstractTableModel,重写getRowCount()getColumnCount()getValueAt(int row, int column)三个核心方法。关键在于dataList成员变量——它是一个List<Student>,所有CRUD操作都直接修改这个列表,然后调用fireTableDataChanged()通知表格刷新。addStudent(Student s)方法里,dataList.add(s); fireTableDataChanged();这两行代码,就是数据变更的源头。

表格初始化:

tableModel = new StudentTableModel(); JTable table = new JTable(tableModel); // 设置列宽,避免文字被截断 table.getColumnModel().getColumn(0).setPreferredWidth(80); // 学号列 table.getColumnModel().getColumn(1).setPreferredWidth(120); // 姓名列 // 双击编辑:启用单元格编辑 table.setDefaultEditor(Object.class, new DefaultCellEditor(new JTextField()));

这里有个易错点:JTable默认不支持双击编辑,必须显式设置setDefaultEditor。但DefaultCellEditor只能编辑文本,如果未来要支持下拉选择班级,就得自定义ComboBoxEditor

CRUD按钮事件绑定:
-新增:点击“添加”按钮,弹出AddStudentDialog(继承BaseDialog)。对话框里有四个JTextField,用户填完点“确定”,AddStudentDialogokButton监听器会收集数据,创建Student对象,调用StudentDAO.addStudent(student)插入数据库,成功后调用tableModel.addStudent(student)更新表格模型。
-查询:顶部有JTextField searchField和“搜索”按钮。监听器执行List<Student> results = StudentDAO.searchStudents(searchField.getText()),然后tableModel.setDataList(results)setDataList()StudentTableModel里自定义方法,清空旧列表,添加新结果,并调用fireTableDataChanged())。
-修改:选中表格某行,点“编辑”按钮。table.getSelectedRow()获取行索引,tableModel.getStudentAt(rowIndex)拿到对应Student对象,传入EditStudentDialog。对话框关闭后,如果用户点了“确定”,则调用StudentDAO.updateStudent(editedStudent)更新数据库,再调用tableModel.updateStudentAt(rowIndex, editedStudent)更新表格模型。
-删除:选中行,点“删除”,弹出确认对话框。确认后,StudentDAO.deleteStudentById(tableModel.getStudentAt(selectedRow).getId())执行删除,成功后tableModel.removeStudentAt(selectedRow)移除模型数据。

实操心得:JTablegetSelectedRow()返回的是视图行号(view row),不是模型行号(model row)。当表格启用了排序(table.setAutoCreateRowSorter(true)),视图行和模型行就不一致了。这个项目没开启排序,所以直接用getSelectedRow()是安全的。但如果未来要加排序功能,必须用table.convertRowIndexToModel(table.getSelectedRow())转换。

3.3 MySQL直连实现:从驱动到事务的完整链路

DBUtil.java是数据库操作的中枢,其设计体现了对JDBC底层的理解:

连接获取:

public static Connection getConnection() throws SQLException { if (dataSource == null) { initDataSource(); } return dataSource.getConnection(); // 从连接池获取 } private static void initDataSource() { dataSource = new BasicDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/studentdb?useSSL=false&serverTimezone=Asia/Shanghai"); dataSource.setUsername("root"); dataSource.setPassword("root"); // 生产环境务必修改! dataSource.setInitialSize(5); dataSource.setMaxActive(20); dataSource.setMaxWait(60000); }
  • useSSL=false:MySQL 8.0+默认要求SSL连接,本地开发可关闭以简化配置。
  • serverTimezone=Asia/Shanghai:解决时区问题,避免存入数据库的时间比系统时间少8小时。
  • initialSize=5:连接池初始创建5个连接,避免首次请求时创建连接的延迟。
  • maxActive=20:最大活跃连接数,超过则等待,防止数据库被打爆。

事务管理:
StudentDAO.deleteStudentById(String id)方法展示了手动事务:

public boolean deleteStudentById(String id) { String sql = "DELETE FROM student WHERE id = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { conn.setAutoCommit(false); // 关闭自动提交 ps.setString(1, id); int rows = ps.executeUpdate(); if (rows > 0) { conn.commit(); // 提交事务 return true; } else { conn.rollback(); // 回滚 return false; } } catch (SQLException e) { throw new DataAccessException("删除学生失败", e); } }

为什么需要手动事务?因为删除操作可能关联其他表(如成绩表),未来扩展时需保证原子性。即使当前单表,养成setAutoCommit(false)的习惯,能避免后续加逻辑时遗漏。

异常处理:
DataAccessException是自定义运行时异常,继承RuntimeException,因此DAO方法无需在throws声明。LoginController捕获它后,统一弹窗提示,而不是让程序崩溃。这种异常分类(数据访问异常 vs 业务逻辑异常 vs UI异常),是工程化的重要标志。

4. 实操部署与运行指南:从零开始的完整流程

4.1 环境准备:JDK、MySQL、IDE的精确版本

这个项目标称“JDK 8+”,但实测在JDK 17上会遇到两个问题,必须提前规避:

  • JDBC驱动兼容性:MySQL官方推荐JDK 8-15使用mysql-connector-java-8.0.33.jar,JDK 17+需用mysql-connector-j-8.1.0.jar(注意包名从mysql-connector-java变为mysql-connector-j)。项目lib目录下提供的是mysql-connector-java-8.0.33.jar,所以强烈建议使用JDK 8u202或JDK 11。若坚持用JDK 17,请替换lib下的jar包,并修改DBUtil.javaClass.forName("com.mysql.cj.jdbc.Driver")Class.forName("com.mysql.cj.jdbc.Driver")(新版驱动类名未变,但jar包不同)。

  • Swing字体渲染:JDK 9+默认启用HiDPI缩放,可能导致login.jpg背景模糊。解决方案是在启动jar时加JVM参数:java -Dsun.java2d.uiScale=1.0 -jar Main.jar。更彻底的方法是,在Main.javamain方法开头添加:
    java System.setProperty("sun.java2d.uiScale", "1.0"); UIManager.setLookAndFeel(UIManager.getSystemLookAndFeel());

MySQL安装要求:5.7或8.0版本,且必须启用local_infile(默认关闭)。因为student.sql脚本里没有CREATE DATABASE语句,它假设你已创建好名为studentdb的数据库。启用local_infile的命令是:

SET GLOBAL local_infile = 1; -- 或在MySQL配置文件my.cnf中添加: [mysqld] local_infile=1

Eclipse配置:.project.classpath文件已适配Eclipse Oxygen(4.7)及以上版本。导入步骤:
1. Eclipse菜单File → Import → General → Existing Projects into Workspace
2. 选择项目根目录(含srcbinlib的文件夹)
3. 勾选项目,点击Finish
4. 右键项目 →Properties → Java Build Path → Libraries → Add External JARs,添加lib下的所有jar包(特别是mysql-connector-java-8.0.33.jarbcprov-jdk15on-1.68.jar
5. 右键src/Main.javaRun As → Java Application

4.2 数据库初始化:三步走,零失误

导入student.sql脚本是部署关键,常见错误是编码不一致导致中文乱码。以下是经过百次验证的无错流程:

第一步:创建数据库(必须指定字符集)

CREATE DATABASE studentdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE studentdb;

注意:utf8mb4而非utf8,因为MySQL的utf8实际只支持3字节UTF-8,无法存储emoji和部分生僻汉字;utf8mb4_unicode_ci是更准确的Unicode排序规则。

第二步:执行student.sql(确保客户端编码一致)
在MySQL命令行中:

mysql -u root -p --default-character-set=utf8mb4 studentdb < student.sql

如果用Navicat或DBeaver等GUI工具,务必在连接属性里将“字符集”设为utf8mb4,然后右键数据库 → “运行SQL文件”,选择student.sql

第三步:验证数据
执行SELECT * FROM student;,应看到一条id='admin'的记录,name字段显示为“系统管理员”,而非乱码。如果出现??,说明字符集设置失败,需重做第一步。

提示:student.sqlINSERT语句的VALUES部分,中文字符串用单引号包裹,且SQL文件本身必须保存为UTF-8无BOM格式。Windows记事本默认保存为ANSI,会导致导入失败。推荐用VS Code打开student.sql,右下角确认编码为“UTF-8”,然后File → Save with Encoding → UTF-8

4.3 运行与调试:从双击jar到排查黑屏

双击运行(Windows):
- 确保Main.jarlib文件夹在同一目录。
- 双击Main.jar,如果弹出“无法打开此文件”,说明JDK未关联jar类型。解决方案:右键Main.jarProperties → Opens with → Change → Browse,找到javaw.exe(通常在C:\Program Files\Java\jdk-xx\bin\下)。
- 首次运行可能黑屏几秒——这是JVM加载mysql-connector和建立连接池的正常现象,耐心等待。

命令行运行(全平台):

# 进入项目根目录 cd /path/to/project # 执行(JDK 8-15) java -cp "bin;lib/*" Main # 执行(JDK 17+,需替换驱动) java -cp "bin;lib/*" Main

-cp "bin;lib/*"表示类路径包含bin目录(class文件)和lib下所有jar包。Linux/macOS用冒号:分隔:-cp "bin:lib/*"

常见黑屏/白屏排查:
| 现象 | 可能原因 | 解决方案 |
|------|----------|----------|
| 双击无反应,任务管理器看不到java进程 |Main.jar未关联JDK,或JDK路径含中文/空格 | 用命令行运行,看报错;重装JDK到纯英文路径 |
| 登录窗口弹出,但背景图login.jpg显示为灰色方块 | 图片路径错误,或login.jpg不在bin/images/下 | 检查src/images/login.jpg是否存在;清理bin目录,重新Build Project |
| 输入账号密码后,点击登录无响应,控制台无输出 |DBUtil.getConnection()超时,MySQL服务未启动或端口被占 | 检查MySQL是否运行(netstat -ano \| findstr :3306);确认DBUtil.java中URL的端口号(默认3306) |
| 登录成功,但主界面表格为空,控制台报SQLException: Table 'studentdb.student' doesn't exist|student.sql未导入,或导入到了错误数据库 | 进入MySQL,执行USE studentdb; SHOW TABLES;,确认有student表 |

5. 常见问题与避坑指南:那些文档里不会写的实战经验

5.1 图片资源加载失败:90%的“找不到文件”都是路径惹的祸

新手最常卡在login.jpg加载失败。根本原因不是图片坏了,而是对Java类路径(classpath)的理解偏差。LoginView.class.getResource("/images/login.jpg")中的/代表类路径根目录,即binsrc的同级目录。所以资源必须放在:

项目根目录/ ├── bin/ │ └── images/ │ └── login.jpg ← 编译后这里必须有 ├── src/ │ └── images/ │ └── login.jpg ← 源码里这里必须有 └── ...

如果把login.jpg直接放在src/下,getResource("login.jpg")才能找到;放在src/images/下,就必须用getResource("/images/login.jpg")。Eclipse的Build Path默认将srcsrc/images都加入classpath,所以只要源码位置对,编译后bin/images/就会自动生成。

避坑技巧:在LoginView.java构造函数里加一行调试代码:
java URL imageUrl = LoginView.class.getResource("/images/login.jpg"); System.out.println("图片路径: " + imageUrl); // 如果为null,说明路径错

5.2 中文乱码的终极解决方案:四层编码统一

从数据库到界面,中文乱码涉及四层编码,缺一不可:

  1. MySQL服务器层:SHOW VARIABLES LIKE 'character_set%';确认character_set_server=utf8mb4
  2. 数据库层:CREATE DATABASE studentdb CHARACTER SET utf8mb4;
  3. 连接层:JDBC URL中?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4
  4. Java源码层:.java文件本身保存为UTF-8(IDE设置),student.sql文件也是UTF-8

漏掉任何一层,都会导致乱码。例如,即使数据库和连接层都设了utf8mb4,但student.sql文件是ANSI编码,那么INSERT INTO student (name) VALUES ('张三');里的“张三”在导入时就被转成了乱码,后续再也无法恢复。

5.3 CRUD操作后表格不刷新:模型与视图的同步陷阱

很多同学发现,点了“添加”按钮,数据库里确实多了记录,但JTable没变化。原因几乎全是:忘记调用fireTableDataChanged()fireTableRowsInserted()StudentTableModeladdStudent()方法必须包含:

public void addStudent(Student student) { dataList.add(student); fireTableRowsInserted(dataList.size() - 1, dataList.size() - 1); // 关键! // 或者用更粗粒度的:fireTableDataChanged(); }

fireTableRowsInserted()fireTableDataChanged()更高效,因为它只通知新增的行,而后者会触发整个表格重绘。但初学者用fireTableDataChanged()更安全,不易出错。

5.4 多线程警告:Swing不是线程安全的

Swing组件必须在事件调度线程(EDT)中创建和修改。这个项目在Main.java里严格遵守:

public static void main(String[] args) { SwingUtilities.invokeLater(() -> { new LoginView().setVisible(true); // 所有Swing操作都在EDT中 }); }

如果在DAO层的addStudent()方法里,直接写JOptionPane.showMessageDialog(null, "添加成功");,就会导致线程不安全警告。正确做法是:DAO只负责数据操作,成功后由Controller(在EDT中)调用JOptionPane

实操心得:在StudentDAO的每个方法末尾,加一句System.out.println("DAO操作完成: " + Thread.currentThread().getName());,你会看到输出是main线程;而在LoginController.handleLogin()里加同样日志,输出是AWT-EventQueue-0——这就是EDT。理解这个,就理解了为什么所有Swing UI操作必须包裹在SwingUtilities.invokeLater()里。

6. 二次开发与功能扩展:从“能用”到“好用”的升级路径

这个项目的设计初衷是“最小可行”,所以预留了大量扩展接口。以下是三个最实用的升级方向,附带具体代码片段:

6.1 添加成绩统计功能:用JFreeChart画柱状图

MainView里增加一个“统计”菜单项,点击后弹出StatisticsDialog。核心是集成jfreechart-1.5.3.jar(项目lib已提供):

// StatisticsDialog.java private void createChart() { // 从DAO获取各班级平均分 Map<String, Double> classAvgScore = StudentDAO.getClassAverageScores(); DefaultCategoryDataset dataset = new DefaultCategoryDataset(); for (Map.Entry<String, Double> entry : classAvgScore.entrySet()) { dataset.addValue(entry.getValue(), "平均分", entry.getKey()); } JFreeChart chart = ChartFactory.createBarChart( "各班级平均成绩", "班级", "平均分", dataset ); ChartPanel chartPanel = new ChartPanel(chart); add(chartPanel, BorderLayout.CENTER); }

StudentDAO.getClassAverageScores()的SQL是:SELECT class, AVG(score) as avg_score FROM student GROUP BY class。这个功能让系统从“信息录入工具”升级为“教学分析助手”。

6.2 支持Excel导入导出:用Apache POI解放双手

MainView增加“导入Excel”按钮。依赖poi-5.2.4.jarpoi-ooxml-5.2.4.jar

// 导入逻辑 private void importFromExcel(File file) { try (FileInputStream fis = new FileInputStream(file); XSSFWorkbook workbook = new XSSFWorkbook(fis)) { XSSFSheet sheet = workbook.getSheetAt(0); for (int i = 1; i <= sheet.getLastRowNum(); i++) { // 跳过标题行 XSSFRow row = sheet.getRow(i); Student student = new Student(); student.setId(row.getCell(0).getStringCellValue()); student.setName(row.getCell(1).getStringCellValue()); student.setClass(row.getCell(2).getStringCellValue()); student.setScore(row.getCell(3).getNumericCellValue()); StudentDAO.addStudent(student); } tableModel.refreshFromDatabase(); // 重新加载全部数据 } }

refreshFromDatabase()StudentTableModel里新增方法,调用StudentDAO.findAllStudents()更新dataList。从此,班主任再也不用手动一条条录新生信息。

6.3 升级为多用户权限系统:从单管理员到角色管理

当前student表复用id/password做登录,但实际教务系统需要区分“管理员”、“教师”、“学生”角色。扩展方案:
- 新增role字段到student表:ALTER TABLE student ADD COLUMN role ENUM('ADMIN','TEACHER','STUDENT') DEFAULT 'STUDENT';
-LoginController.authenticate()返回Student对象后,检查student.getRole()
java if ("ADMIN".equals(authenticatedStudent.getRole())) { new AdminMainView(authenticatedStudent).setVisible(true); } else if ("TEACHER".equals(authenticatedStudent.getRole())) { new TeacherMainView(authenticatedStudent).setVisible(true); }
-AdminMainView提供“用户管理”菜单,可新增教师账号、重置密码、分配班级。

这个扩展让系统真正具备了生产环境可用性,而不仅仅是课程设计作业。

我在实际带学生做这个项目时,最深的体会是:一个能跑起来的系统,比十个完美的设计图更有教学价值。当学生第一次看到自己输入的“张三,2023001,计算机2301,95.5”出现在表格里,那种真实的成就感,是任何理论讲解都无法替代的。这个项目没有炫酷的动画,没有复杂的网络协议,它只是用最扎实的Swing组件、最朴实的JDBC连接、最规范的MVC分层,把“数据从键盘进入内存,再写入硬盘,最后在屏幕上呈现”这个闭环,清清楚楚地展现在你面前。如果你已经走到这里,不妨现在就打开MySQL,执行那三条建库建表语句,然后双击Main.jar——那个熟悉的登录框,就是你通往Java桌面应用世界的第一扇门。

本文还有配套的精品资源,点击获取

简介:用Java Swing开发的学籍管理桌面程序,启动就能用,自带登录界面和主操作窗口,支持学生姓名、学号、班级、成绩等基本信息的添加、搜索、编辑和删除。代码按MVC分层,src目录下Model存数据结构,DAO负责数据库交互,View放界面组件,Util提供工具方法,base包含基础类,结构清晰适合学习和修改。附带student.sql建表脚本,导入MySQL后自动创建学生表,无需手动建库建表。bin目录已编译好class文件,.classpath和.project适配Eclipse,JDK 8及以上环境双击jar或运行Main类即可启动。图片资源login.jpg和main.jpg分别是登录页和主界面截图,方便快速确认效果。整个工程开箱即用,适合Java GUI初学者练手、高校课程设计或小型教学单位临时管理学生信息。


本文还有配套的精品资源,点击获取