Java Swing学生信息管理系统(带MySQL连接与完整CRUD功能)
本文还有配套的精品资源,点击获取
简介:一个开箱即用的Java桌面程序,用Swing搭建图形界面,通过JDBC连接本地MySQL数据库管理学生信息。主窗口StudentTestWindow提供入口,点击按钮可打开新增(StudentAddWindow)或修改(StudentUpdataWindow)窗口;所有数据库操作由PreparedSQL类封装,使用PreparedStatement执行增删改查,有效防止SQL注入;DBConnection_MySql类集中配置数据库连接参数(URL、用户名、密码、驱动类),统一处理连接获取与关闭逻辑。支持中文姓名、班级等字段的录入和显示,表结构简单明确(student表),只需提前在MySQL中创建对应数据库即可运行。项目采用清晰的单文件单职责设计,每个Java文件功能独立,适合初学者理解GUI事件响应、数据库交互流程、预编译SQL写法以及基础分层意识(如界面层与数据访问层分离)。Eclipse导入后无需额外插件或Maven配置,直接编译运行。
1. 项目概述:为什么这个学生管理系统是Java初学者的“通关钥匙”
你是不是也经历过这样的阶段:学完Swing组件,能画出按钮和表格,但一到“点一下按钮就把数据存进数据库”,脑子就卡住?或者写了个JDBC连接,测试时抛出ClassNotFoundException或SQLException: Access denied,翻遍教程却找不到本地MySQL到底该怎么配——驱动放哪、URL怎么写、密码空不空、时区要不要加serverTimezone=GMT%2B8?这个Java Swing学生信息管理系统,就是我当年带第一批实习生时,亲手拆解、重写、压测过三轮后定稿的“教学锚点”。它不是炫技的Demo,而是一套可触摸、可打断、可逐行调试的真实工作流切片。核心关键词——Java Swing、MySQL连接、学生管理、CRUD操作、JDBC预编译——每一个都不是孤立概念,而是被拧进同一个螺丝口里的真实零件:Swing的ActionListener如何把用户点击翻译成PreparedStatement.executeUpdate();DBConnection_MySql里那行Class.forName("com.mysql.cj.jdbc.Driver")为什么必须在JDK 8+环境下显式调用;PreparedSQL中"INSERT INTO student (name, age, class_name) VALUES (?, ?, ?)"里的问号,怎样在ps.setString(1, nameField.getText())这行代码里完成从界面字符串到数据库字段的精准映射。它不教你“面向对象是什么”,而是让你在StudentAddWindow里右键点击addButton.addActionListener(...),F3跳转进去,亲眼看见事件如何触发PreparedSQL.addStudent(...),再一路跟到DBConnection_MySql.getConnection()拿到连接对象——这种链路感,是任何PPT课件都给不了的。适合谁?不是要立刻写出Spring Boot微服务的高手,而是刚搞懂public static void main(String[] args)、能写个控制台学生成绩录入、现在想迈出桌面应用第一步的你。它运行起来就是一个干净的窗口,没有弹窗广告,不联网验证,不依赖云服务,所有逻辑都在你本地Eclipse里,双击StudentTestWindow.main()就能启动,就像打开一个记事本那样直接。
2. 整体架构与设计思路:单文件职责制背后的工程直觉
2.1 为什么拒绝“大杂烩”式单文件?——从混乱到清晰的演化路径
我见过太多初学者写的Swing程序:一个Main.java塞了界面绘制、事件监听、数据库连接、SQL拼接、结果处理……改一行代码,全局报错。这个项目强制采用“单文件单职责”,不是为了炫技,而是模拟真实开发中“关注点分离”的第一课。你看目录结构:StudentTestWindow.java只干一件事——搭主界面、放按钮、响应点击;StudentAddWindow.java只负责新增窗口的布局、输入校验、提交动作;PreparedSQL.java是纯粹的数据访问层(DAL),里面没有JFrame、没有JTextField,只有Connection、PreparedStatement和ResultSet。这种拆分不是教条,而是有血泪教训的。比如早期版本我把添加逻辑直接写在StudentAddWindow的actionPerformed里,结果发现修改功能也要复用同样的插入SQL,就得复制粘贴——一旦数据库表字段增加,两处都要改,漏改一处就导致数据不一致。后来我把SQL操作全抽到PreparedSQL,addStudent()和updateStudent()方法签名清晰定义为public boolean addStudent(String name, int age, String className),调用方只管传参,不管底层是INSERT还是UPDATE,也不用操心try-catch怎么包SQLException。这就是MVC雏形的落地:StudentAddWindow是View(视图),PreparedSQL是Model(模型),而按钮点击事件就是Controller(控制器)的触发点。你可能觉得“不就是几个Java文件吗”,但当你在Eclipse里右键PreparedSQL.java选择“Open Call Hierarchy”,看到StudentAddWindow和StudentUpdataWindow都指向它同一个addStudent()方法时,那种模块间松耦合的直观感,比十页UML图都管用。
2.2 JDBC连接管理:DBConnection_MySql类为何必须“独占”连接逻辑?
数据库连接是整个系统的命脉,也是初学者最容易踩坑的地方。很多人写Connection conn = DriverManager.getConnection(...)放在每个DAO方法里,结果忘了conn.close(),跑几次程序就报“Too many connections”。DBConnection_MySql.java的存在,就是把这种高危操作锁死在一个地方。它的核心不是“连上数据库”,而是“安全地连、可控地关”。看它的关键设计:
-静态连接池雏形:虽然没用HikariCP这类专业池,但它用private static Connection connection = null;配合synchronized块实现最简化的连接复用。第一次调用getConnection()时真正创建连接,后续调用直接返回已存在的connection对象,避免频繁握手开销。
-驱动加载的兼容性处理:static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { ... } }这段静态块,解决了JDK 6-7时代自动注册驱动的遗留问题。MySQL 8.0+驱动要求显式加载,否则getConnection()会抛ClassNotFoundException,而这个类把它兜底了。
-URL参数的实战填坑:private static final String URL = "jdbc:mysql://localhost:3306/student_db?useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true";这串URL里每个参数都是实测来的:useSSL=false绕过本地MySQL默认不启用SSL的报错;serverTimezone=GMT%2B8解决中文Windows系统时区识别错误导致的日期乱码;allowPublicKeyRetrieval=true应对MySQL 8.0.21+新认证插件的密钥获取需求。这些不是凭空写的,是我在Win10+MySQL 8.0.33环境反复试错后固化下来的。如果你删掉serverTimezone,往数据库插一条记录,查出来create_time字段可能是1970年——这种细节,文档里不会写,但生产环境天天见。
2.3 PreparedStatement防注入:不只是“安全”,更是“类型安全”的第一道门
很多人以为PreparedStatement只是防SQL注入,其实它更大的价值在于类型强约束。看PreparedSQL.addStudent()里的代码:
String sql = "INSERT INTO student (name, age, class_name) VALUES (?, ?, ?)"; PreparedStatement ps = connection.prepareStatement(sql); ps.setString(1, name); // 明确告诉JDBC:第1个?是字符串 ps.setInt(2, age); // 第2个?是整数 ps.setString(3, className); // 第3个?是字符串对比Statement的字符串拼接:"INSERT INTO student VALUES ('" + name + "', " + age + ", '" + className + "')",问题在哪?如果用户在姓名框输入张三'; DROP TABLE student; --,拼接后SQL变成INSERT INTO student VALUES ('张三'; DROP TABLE student; --', 20, '计算机1班'),直接执行危险语句。而PreparedStatement会把setString(1, "张三'; DROP TABLE student; --")当作纯文本字符串处理,插入的姓名就是字面量张三'; DROP TABLE student; --,毫无威胁。更重要的是类型安全:如果age字段是int,你传入ps.setString(2, "abc"),JDBC驱动会在执行前就抛SQLException,而不是等数据入库后才发现类型不匹配。这种编译期/运行期的双重防护,让初学者在写CRUD时,天然避开90%的数据类型错误和注入风险。这也是为什么项目摘要里强调“所有CRUD操作均使用PreparedStatement”——它不是一个可选项,而是这个系统能稳定运行的基石。
3. 核心细节解析与实操要点:从代码行到运行效果的完整映射
3.1 主窗口StudentTestWindow:按钮事件如何精准触发子窗口?
StudentTestWindow是整个系统的门面,它的核心不是炫酷UI,而是事件路由的可靠性。看它的关键代码段:
JButton addButton = new JButton("新增学生"); addButton.addActionListener(e -> { StudentAddWindow addWindow = new StudentAddWindow(); addWindow.setVisible(true); }); JButton updateButton = new JButton("修改学生"); updateButton.addActionListener(e -> { StudentUpdataWindow updateWindow = new StudentUpdataWindow(); updateWindow.setVisible(true); });这里有两个极易被忽略的细节:
-窗口实例化时机:new StudentAddWindow()写在actionPerformed内部,而不是类成员变量里。这意味着每次点击“新增”按钮,都会创建一个全新的窗口实例。好处是内存干净——关闭窗口后对象可被GC回收;坏处是如果用户疯狂点击,会瞬间弹出多个新增窗口。实际项目中我们会加if (addWindow == null || !addWindow.isVisible()) { addWindow = new StudentAddWindow(); }做单例控制,但教学版故意留白,让你自己动手加这个判断,理解“状态管理”的必要性。
-setVisible(true)的位置:必须在new之后立即调用,否则窗口对象创建了但不可见。我见过学员把setVisible(true)写在StudentAddWindow构造方法末尾,结果addWindow.setVisible(true)又调一次,导致窗口闪烁。正确做法是构造方法只负责初始化组件(initComponents()),显示逻辑交给调用方统一控制。
-中文显示保障:JFrame默认字体在Windows上可能不支持中文,导致按钮文字显示为方块。解决方案是在StudentTestWindow构造方法开头加:
UIManager.put("Label.font", new Font("微软雅黑", Font.PLAIN, 14)); UIManager.put("Button.font", new Font("微软雅黑", Font.PLAIN, 14));这是Swing跨平台字体适配的硬知识,不是可有可无的美化,而是功能可用的前提。
3.2 新增窗口StudentAddWindow:输入校验与数据流转的闭环设计
StudentAddWindow表面是个简单的表单,实则藏着GUI编程的核心逻辑闭环。它的流程是:用户输入 → 点击确定 → 校验格式 → 封装数据 → 调用DAO → 反馈结果。我们拆解其中最关键的三步:
-输入校验的务实主义:没有用正则表达式搞复杂验证,而是聚焦业务刚需。姓名不能为空(nameField.getText().trim().isEmpty()),年龄必须是数字且在15-35之间(Integer.parseInt(ageField.getText())捕获NumberFormatException),班级名长度限制在20字符内(classNameField.getText().length() <= 20)。这种校验不是为了炫技,而是防止PreparedStatement执行时因数据超长或类型错误而崩溃。比如年龄字段若允许输入abc,Integer.parseInt("abc")直接抛异常,程序闪退;而提前拦截,弹出JOptionPane.showMessageDialog(this, "年龄必须是数字!"),用户体验陡然提升。
-数据封装的轻量级DTO思想:虽然没建Student实体类,但在addButton点击事件里,我们手动组装参数:
String name = nameField.getText().trim(); int age = Integer.parseInt(ageField.getText()); String className = classNameField.getText().trim(); boolean success = PreparedSQL.addStudent(name, age, className);这其实就是最朴素的DTO(Data Transfer Object)模式:把界面层的零散输入,聚合成符合DAO层接口要求的参数序列。未来你扩展功能时,只需新建Student类,把这三行替换成Student stu = new Student(name, age, className); PreparedSQL.addStudent(stu);,上层代码几乎不用动。
-操作反馈的即时性:addStudent()返回boolean,成功则弹窗"添加成功!"并清空表单(nameField.setText(""); ageField.setText("");),失败则弹窗"添加失败,请检查网络或数据库!"。这里刻意没写具体错误原因(如Duplicate entry),因为初学者还没接触异常分类处理。但你在PreparedSQL.addStudent()里能看到catch (SQLException e) { e.printStackTrace(); return false; },这就是埋下的伏笔——等你学到e.getSQLState(),就能区分是主键冲突还是连接超时,给出更精准提示。
3.3 数据访问层PreparedSQL:CRUD方法背后的SQL语义与事务意识
PreparedSQL.java是整个系统的数据引擎,它的每个方法都对应一条明确的SQL语义。我们以updateStudent()为例,深挖其设计逻辑:
public static boolean updateStudent(int id, String name, int age, String className) { String sql = "UPDATE student SET name = ?, age = ?, class_name = ? WHERE id = ?"; try (Connection conn = DBConnection_MySql.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, name); ps.setInt(2, age); ps.setString(3, className); ps.setInt(4, id); // WHERE条件必须放在最后 int rows = ps.executeUpdate(); return rows > 0; } catch (SQLException e) { e.printStackTrace(); return false; } }- WHERE子句的致命位置:
ps.setInt(4, id)必须在ps.setString(3, className)之后,因为?的序号严格按SQL字符串中出现顺序编号。如果写反了,id值会被赋给class_name字段,导致数据错乱。这是初学者高频错误,建议在IDE里把SQL字符串写成多行,用注释标出每个?的用途:
-- UPDATE student SET name = ?, age = ?, class_name = ? WHERE id = ? -- 1 2 3 4- try-with-resources的自动释放:
try (Connection conn = ...; PreparedStatement ps = ...)语法确保无论执行成功与否,conn和ps都会在}结束时自动close()。这比手写finally{conn.close();}更可靠,避免因异常跳过close()导致连接泄漏。这是JDK 7引入的语法糖,但教学版必须用,因为它代表现代Java资源管理的标准实践。 - 事务意识的萌芽:当前所有方法都是单SQL操作,看似无需事务。但当你需要“先删旧记录,再插新记录”时(比如修改学生班级同时更新班级统计表),就必须用
conn.setAutoCommit(false)开启事务,conn.commit()提交,conn.rollback()回滚。PreparedSQL的结构已经预留了扩展空间——所有方法都接受Connection参数(当前是内部获取),未来可改为public static boolean updateStudent(Connection conn, int id, ...),由上层统一控制事务边界。这种设计前瞻性,是优秀代码和凑合代码的本质区别。
4. 实操过程与核心环节实现:从零开始搭建可运行环境的完整指南
4.1 MySQL环境准备:创建数据库与student表的精确指令
项目摘要说“需提前创建对应数据库和student表”,这不是一句虚话,而是运行前必须完成的硬性步骤。以下是我在Windows 10 + MySQL 8.0.33环境下实测通过的完整指令,每一步都附带原理说明:
1.启动MySQL服务:确保服务已运行。Win+R输入services.msc,找到MySQL80,状态应为“正在运行”。若未启动,右键“启动”。这是基础,但新手常忽略——服务没开,DriverManager.getConnection()必然超时。
2.登录MySQL命令行:打开CMD,执行mysql -u root -p,输入root密码。注意:-u后不能有空格,-p后直接跟密码(如-proot123)或回车后手动输入。这是最直接的连接验证方式,比任何GUI工具都可靠。
3.创建数据库:执行CREATE DATABASE student_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。关键点:
-student_db是数据库名,必须与DBConnection_MySql.java中的URL一致(jdbc:mysql://localhost:3306/student_db);
-CHARACTER SET utf8mb4是必须的!utf8在MySQL中实际是utf8mb3,不支持emoji和部分生僻汉字,utf8mb4才是真正的UTF-8,确保中文姓名(如“䶮”、“龘”)能完整存储;
-COLLATE utf8mb4_unicode_ci指定排序规则,ci表示case-insensitive(大小写不敏感),符合中文检索习惯。
4.创建student表:执行以下建表语句:
USE student_db; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT '学生姓名', age INT NOT NULL CHECK (age >= 15 AND age <= 35) COMMENT '学生年龄', class_name VARCHAR(50) NOT NULL COMMENT '班级名称', create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;AUTO_INCREMENT让id自增,避免手动维护ID;CHECK (age >= 15 AND age <= 35)是MySQL 8.0.16+支持的行级约束,比Java层校验更底层、更可靠;TIMESTAMP DEFAULT CURRENT_TIMESTAMP自动记录插入时间,方便审计;ENGINE=InnoDB是必须的,它支持事务和外键(虽本项目未用,但为扩展留余地);DEFAULT CHARSET=utf8mb4再次确认字符集,杜绝乱码根源。
执行完后,用SHOW CREATE TABLE student;查看建表语句是否包含utf8mb4,这是最终验证。
4.2 Eclipse项目导入与依赖配置:零配置运行的关键三步
项目声明“Eclipse导入后无需额外插件或Maven配置”,这基于一个前提:所有依赖已打包进项目。但新手常卡在“导入后一堆红色波浪线”。以下是精确到点击步骤的操作指南:
1.导入项目:Eclipse菜单栏File → Import → General → Existing Projects into Workspace,点击Browse选择解压后的项目根目录(含.project文件),勾选项目名,Finish。此时项目名左侧应有蓝色小球图标,表示已识别为Java项目。
2.添加MySQL驱动JAR:这是最关键的一步!项目资源包里没有mysql-connector-java-x.x.x.jar,必须手动添加。下载地址:https://dev.mysql.com/downloads/connector/j/(选Platform Independent的ZIP包)。解压后找到mysql-connector-java-8.0.33.jar(版本需与DBConnection_MySql.java中Class.forName("com.mysql.cj.jdbc.Driver")匹配)。在Eclipse中右键项目 →Build Path → Configure Build Path → Libraries → Add External JARs,选中该JAR文件。添加后,DBConnection_MySql.java中import com.mysql.cj.jdbc.Driver;不再报错。
3.验证数据库连接参数:打开DBConnection_MySql.java,检查以下四行:
private static final String URL = "jdbc:mysql://localhost:3306/student_db?useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true"; private static final String USERNAME = "root"; private static final String PASSWORD = "your_password_here"; // 修改为你的真实密码 private static final String DRIVER = "com.mysql.cj.jdbc.Driver";URL中的localhost若MySQL安装在其他机器,需改为IP(如192.168.1.100);PASSWORD必须是你MySQL root用户的实际密码,不能留空或写root(除非你真设了这个密码);DRIVER类名必须与JAR包版本匹配:MySQL 5.x用com.mysql.jdbc.Driver,8.x用com.mysql.cj.jdbc.Driver,错一个字母就ClassNotFoundException。
完成这三步,右键StudentTestWindow.java→Run As → Java Application,主窗口应正常弹出。如果报Access denied for user 'root'@'localhost',说明密码错误;如果报Unknown database 'student_db',说明数据库未创建或名字不匹配。
4.3 CRUD功能全流程演示:从新增到查询的端到端验证
光有窗口弹出不算成功,必须验证数据真正落库。以下是完整的端到端测试流程,每一步都附带预期结果和排查线索:
-新增学生:点击主窗口“新增学生”,在新增窗口输入姓名“李四”、年龄“20”、班级“软件工程2班”,点击“确定”。预期:弹出“添加成功!”提示框,窗口自动清空。验证:打开MySQL命令行,执行SELECT * FROM student_db.student;,应看到一条新记录,id为1(因AUTO_INCREMENT),create_time为当前时间。若无记录,检查PreparedSQL.addStudent()中ps.executeUpdate()是否被调用(可在该行打断点调试)。
-查询学生:当前项目未提供查询窗口,但PreparedSQL已封装getAllStudents()方法。你可以在StudentTestWindow.main()末尾临时添加:
List<Map<String, Object>> students = PreparedSQL.getAllStudents(); for (Map<String, Object> stu : students) { System.out.println(stu.get("name") + ", " + stu.get("age") + ", " + stu.get("class_name")); }运行后,控制台应打印出所有学生信息。这是验证SELECT语句是否正确的最快方式。
-修改学生:点击“修改学生”,在修改窗口输入id=1,其他字段改为“王五”、“21”、“人工智能1班”,点击“更新”。预期:弹出“更新成功!”。验证:再次SELECT * FROM student_db.student;,第一条记录的字段应已变更。若未变,检查updateStudent()中WHERE id = ?的参数是否正确绑定(ps.setInt(4, id))。
-删除学生(隐含功能):项目未提供删除按钮,但PreparedSQL.deleteStudent(int id)方法已存在。你可以仿照上面,在main()里调用PreparedSQL.deleteStudent(1);,再查表确认记录消失。这证明CRUD四大操作全部就绪,只待你按需集成到GUI中。
提示:所有操作中,若遇到
SQLException,务必查看控制台打印的完整堆栈。常见错误码:08S01(连接中断)、23000(主键冲突)、45000(自定义错误)。不要只看e.getMessage(),e.getSQLState()和e.getErrorCode()才是精准定位的钥匙。
5. 常见问题与排查技巧实录:那些文档里不会写的“踩坑现场”
5.1 连接失败类问题:从“Access denied”到“Communications link failure”的逐层诊断
这是新手启动项目时最高频的报错,我整理了一份基于真实日志的速查表,按发生概率排序:
| 错误信息(控制台截取) | 最可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
Access denied for user 'root'@'localhost' | MySQL密码错误或用户权限不足 | 1. 在MySQL命令行执行SELECT User,Host FROM mysql.user;确认root用户存在;2. 执行SELECT plugin FROM mysql.user WHERE User='root';确认认证插件是mysql_native_password | 若plugin是caching_sha2_password,执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES; |
Communications link failure | MySQL服务未启动或端口被占用 | 1. Win+R输入services.msc,确认MySQL80状态为“正在运行”;2. CMD执行netstat -ano | findstr :3306,检查3306端口是否被其他进程占用 | 若服务未启动,手动启动;若端口被占,修改MySQL配置文件my.ini中port=3307,并同步更新DBConnection_MySql.URL |
Unknown database 'student_db' | 数据库未创建或URL中数据库名拼写错误 | 1. MySQL命令行执行SHOW DATABASES;,确认student_db存在;2. 检查DBConnection_MySql.URL中student_db是否多空格或大小写错误(Linux下数据库名区分大小写) | 创建数据库:CREATE DATABASE student_db CHARACTER SET utf8mb4;;修正URL中的数据库名 |
The server time zone value 'xxx' is unrecognized | MySQL服务器时区与JDBC URL中serverTimezone不匹配 | 1. MySQL命令行执行SELECT @@global.time_zone, @@session.time_zone;;2. 查看Windows系统时区(控制面板→时钟和区域) | 若MySQL返回SYSTEM,Windows是东八区,则URL中serverTimezone=GMT%2B8;若MySQL返回+08:00,则URL中serverTimezone=GMT%2B8或serverTimezone=Asia/Shanghai |
注意:所有连接问题,终极验证法是脱离Java,用MySQL命令行直连:
mysql -h localhost -P 3306 -u root -p student_db。如果命令行能连,说明Java代码或驱动有问题;如果命令行也连不上,一定是MySQL服务或网络配置问题。
5.2 中文乱码类问题:从控制台问号到数据库方块的全链路修复
中文乱码是跨语言开发的“经典幽灵”,它可能出现在三个环节:Java源文件编码、JDBC连接、MySQL数据库/表字符集。以下是分环节排查法:
-环节一:Java源文件编码:Eclipse中右键项目 →Properties → Resource → Text file encoding,必须设为UTF-8。若为GBK,String name = "张三";在编译时就被转成乱码字节,后续一切努力白费。验证:在StudentAddWindow构造方法里加System.out.println("测试中文:" + "张三");,控制台输出应为“张三”,而非“??”。
-环节二:JDBC连接URL:DBConnection_MySql.URL中必须包含characterEncoding=utf8mb4参数(尽管useUnicode=true已隐含,但显式声明更保险)。完整URL示例:jdbc:mysql://localhost:3306/student_db?useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&characterEncoding=utf8mb4。
-环节三:MySQL字符集:执行SHOW VARIABLES LIKE 'character_set%';,确认character_set_client、character_set_connection、character_set_database、character_set_server均为utf8mb4。若不是,需修改MySQL配置文件my.ini,在[mysqld]下添加:
[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci然后重启MySQL服务。
实操心得:我曾为一个乱码问题耗时两天,最终发现是Eclipse工作空间编码被某个插件悄悄改成了GBK。所以,当怀疑乱码时,第一件事不是改数据库,而是打开Eclipse的
Window → Preferences → General → Workspace,确认Text file encoding是UTF-8——这是90%乱码问题的起点。
5.3 GUI界面类问题:从窗口空白到按钮失效的视觉调试法
Swing界面问题往往无声无息,但影响体验。以下是几个典型场景的快速修复:
-窗口弹出但内容为空白:大概率是StudentAddWindow构造方法中,initComponents()调用位置错误。正确顺序是:先super("新增学生"),再setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE),然后initComponents(),最后pack()和setLocationRelativeTo(null)。若initComponents()写在pack()之后,组件未被添加到容器,自然空白。
-按钮点击无反应:检查addActionListener()是否在组件创建后调用。常见错误:JButton button = new JButton("确定");之后,忘记写button.addActionListener(...),或者写成了addButton.addActionListener(...)(变量名写错)。调试技巧:在actionPerformed方法第一行加System.out.println("按钮被点击了!");,运行看控制台是否有输出。
-中文显示为方块:如前所述,必须设置UIManager.put("Button.font", new Font("微软雅黑", Font.PLAIN, 14));。若仍无效,尝试Font font = new Font("SimSun", Font.PLAIN, 14);(宋体),这是Windows最通用的中文字体。
最后分享一个小技巧:当GUI行为诡异时,不要急着改代码,先在
StudentTestWindow构造方法末尾加一行System.out.println("主窗口初始化完成");,运行看这行是否打印。如果没打印,说明构造方法根本没执行完,问题在前面的组件创建或布局管理器设置上;如果打印了,说明问题在事件监听或后续逻辑。这种“打桩法”,是定位GUI问题的黄金法则。
6. 项目扩展与进阶思考:从教学Demo到生产级应用的演进路径
这个学生管理系统,绝不是终点,而是你技术成长的起始坐标。基于它,你可以沿着三条清晰路径向上演进,每一步都对应真实开发能力的跃迁:
-路径一:架构升级——从单文件到分层MVC
当前项目已有MVC雏形,但还不够“标准”。下一步,你可以:
1. 创建model包,放入Student.java实体类,用private String name; private int age;封装属性,配getter/setter;
2. 创建dao包,将PreparedSQL.java重构为StudentDao.java,方法签名改为public Student findById(int id)、public List<Student> findAll(),返回实体对象而非Map;
3. 创建controller包,新建StudentController.java,它持有StudentDao引用,接收StudentAddWindow传来的原始数据,组装成Student对象,再调用dao.add(student)。
这样,StudentAddWindow(View)完全不知道数据库,StudentDao(Model)完全不知道Swing,StudentController(Controller)只负责协调。这就是教科书级的MVC,也是Spring MVC的简化版原型。
- 路径二:功能增强——从CRUD到业务闭环
当前CRUD是原子操作,但真实业务需要关联。例如: - 班级管理:新建
class表,student表增加class_id外键,StudentAddWindow中班级选择从JTextField改为JComboBox,下拉项从SELECT * FROM class动态加载; - 成绩管理:新增
score表,关联student.id,在StudentTestWindow添加“查看成绩”按钮,弹出新窗口展示该生各科成绩及平均分; 文件导出:在主窗口加“导出Excel”按钮,用Apache POI库将
getAllStudents()结果写入.xlsx文件。
这些功能,每一个都能让你深入理解外键约束、多表查询、第三方库集成等硬技能。路径三:技术栈演进——从Swing到现代化前端
Swing是学习GUI的绝佳入口,但生产环境已转向Web。你可以用这个项目的数据层作为后端:
1. 将PreparedSQL改造为Spring Boot的@Repository,用JdbcTemplate替代原生JDBC;
2. 写@RestController暴露/api/students接口,返回JSON;
3. 用Vue.js或React写前端页面,调用这些API,实现和Swing一样的增删改查。
这时你会发现,DBConnection_MySql的连接管理思想、PreparedSQL的SQL封装模式、甚至StudentTestWindow的事件响应逻辑,在Web开发中依然适用——只是载体从JButton变成了<button @click="addStudent">。这种底层能力的迁移,才是技术学习的终极目标。
我个人在实际教学中发现,坚持走完这三条路径的学生,三个月后基本能独立完成企业级Java Web项目的模块开发。他们不再问“Swing有什么用”,而是明白:所有框架,都是对基础原理的封装;所有高级特性,都是对简单逻辑的抽象。这个项目的价值,不在于它多完美,而在于它足够简单,让你能看清每一行代码背后的真实世界。当你某天在Spring Boot项目里看到@Transactional注解时,会心一笑——哦,原来这就是当年PreparedSQL里那个没展开的conn.setAutoCommit(false)啊。
本文还有配套的精品资源,点击获取
简介:一个开箱即用的Java桌面程序,用Swing搭建图形界面,通过JDBC连接本地MySQL数据库管理学生信息。主窗口StudentTestWindow提供入口,点击按钮可打开新增(StudentAddWindow)或修改(StudentUpdataWindow)窗口;所有数据库操作由PreparedSQL类封装,使用PreparedStatement执行增删改查,有效防止SQL注入;DBConnection_MySql类集中配置数据库连接参数(URL、用户名、密码、驱动类),统一处理连接获取与关闭逻辑。支持中文姓名、班级等字段的录入和显示,表结构简单明确(student表),只需提前在MySQL中创建对应数据库即可运行。项目采用清晰的单文件单职责设计,每个Java文件功能独立,适合初学者理解GUI事件响应、数据库交互流程、预编译SQL写法以及基础分层意识(如界面层与数据访问层分离)。Eclipse导入后无需额外插件或Maven配置,直接编译运行。
本文还有配套的精品资源,点击获取
