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

资讯详情

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

JavaSwing+MySQL+JDBC人事管理系统数据库课程设计实现详解

JavaSwing+MySQL+JDBC人事管理系统数据库课程设计实现详解

简介:一份基于Java Swing + MySQL + JDBC的人事管理系统课程设计资源,面向需要完成数据库课程设计或毕业设计的计算机专业学生,也适合初学者学习桌面程序与数据库联动开发的完整思路,帮助解决数据库设计、界面编码与系统整合等常见课程设计难点。资源包共24个文件,内含12张系统运行界面截图、11个Java源文件与1个SQL数据库脚本,压缩后仅143KB,从目录上即可区分源码、截图与数据库脚本,便于快速定位界面展示、业务逻辑与建表数据。已有3878人学习下载,常用于人事管理类选题的参考与二次开发。通过源码能够掌握JDBC连接、登录验证、数据增删改查等核心实现,SQL脚本便于在不同版本MySQL中导入调试;界面截图可对照系统功能快速了解模块划分与操作流程。资源说明中提到的运行错误多由数据库版本差异引起,属于可自行解决的基础问题,适合有一定Java基础的学习者直接使用或改造。

1. 数据库课程设计人事管理系统:先搞懂这东西到底要交什么

第一次做 javaswing+mysql+jdbc+数据库 这套组合的人事管理系统时,我花了一周写业务代码,结果发现真正卡住我的不是 Swing 界面,也不是增删改查的 SQL,而是环境配置、驱动加载、时区和编码这些“看不见”的环节。等把这些坑踩完回头再看,这个课题设计的核心其实只干三件事:用 JavaSwing 画界面、用 JDBC 连 MySQL 8、用人事数据把数据库设计里的表、约束、事务串起来。

这个方案能解决什么?对正在做数据库课程设计的人来说,它是一套能直接复现的骨架:部门表、员工表、薪资表加登录权限,标准的人事管理增删改查。对你动手能力的价值在于,整套代码不依赖 MyBatis、不依赖 Spring,所有连接和操作都走原生 JDBC,数据库设计是否合理一眼就能看出来,也最容易被答辩老师追问。适合谁?适合数据库理论学完但没写过完整项目的人,也适合想用最少的框架依赖把 Java 和 MySQL 打通的人。本文不贴完整源码,但会把表结构、连接工具类、界面绑定和避坑点全部讲清楚,照着写就能跑通。

2. 先把 6 张表设计对:人事系统的地基决定后面所有代码

2.1 表结构设计:业务表与编码表分开建

人事管理系统不管界面多简单,数据库表至少要覆盖真实人事管理的常见概念。很多课设只建一张员工表和一个用户表,这样虽然能交,但答辩时老师问“部门和员工的关系怎么体现”,就答不上来。常见做法是分出 5 张业务表加 2 张辅助表。

员工表(t_employee)是核心,字段我建议这样设计:emp_id 为主键自增,emp_no 作为员工工号要带唯一约束,因为真实系统里离职再入职不能重复编号;emp_name 用 varchar(50),别用 char,char 会补空格导致查询对比时出错。dept_id 是外键,指向部门表。入职日期 hire_date 用 date 类型,薪资用 decimal(10,2) 而不是 float,float 算工资时会产生小数误差,decimal 才能保证金额准确。

部门表(t_dept)最简单,dept_id 主键自增,dept_name 唯一。这里有个容易忽略的点:要给 manager_id 字段,指向员工表的 emp_id,表示部门负责人。如果员工表也存 dept_id,部门和员工就形成了双向引用,建表顺序必须注意——先建员工表会让外键报错,因为部门表还不存在。所以建表顺序应该是:t_user → t_dept → t_employee → t_salary,先建被引用的表。

薪资表(t_salary)单独拆出来而不是把工资字段放进员工表,是因为一个员工可能有多次调薪记录。设计成 salary_id 主键、emp_id 外键、base_salary、bonus、deduction、pay_date 四个业务字段。这个拆分能体现你对第三范式的理解,答辩时是很加分的点。

登录用户表(t_user)不要和员工表合并,因为课程设计的登录系统通常要区分管理员和普通员工两种角色,user_id、username、password、role 四个字段就够了,role 用 int 或 varchar 存,建议用 varchar 存 'admin' 和 'user',可读性更好。password 字段记得用 varchar(64),以后如果要存 MD5 或 SHA-256 的散列值,32 或 64 位长度都放得下。

2.2 建表 SQL:把约束和字符集一次写对

直接在 MySQL 8 的客户端里执行下面这段,建议用 navicat 或 mysql workbench 的查询窗口跑。字符集统一用 utf8mb4,因为 mysql8 默认就是 utf8mb4,它能存 emoji 和生僻字,避免后面乱码排查。

CREATE DATABASE hrms CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hrms; CREATE TABLE t_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role VARCHAR(20) DEFAULT 'user' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_dept ( dept_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL UNIQUE, manager_id INT NULL, CONSTRAINT fk_dept_manager FOREIGN KEY (manager_id) REFERENCES t_employee(emp_id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意上面这段我没按顺序写,t_dept 里引用了 t_employee,实际执行时如果员工表还不存在,外键会报错。正确做法是先把 t_employee 建好再建 t_dept,或者在建表语句里暂时不写 manager_id 的外键,等三个表都建完再用 ALTER TABLE 补上。课程设计里经常出现“创建表顺序混乱导致外键失败”的问题,你在答辩时可以直接说“外键约束我放到最后统一加”,这是很稳妥的做法。

继续建员工表和薪资表:

CREATE TABLE t_employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL UNIQUE, emp_name VARCHAR(50) NOT NULL, gender CHAR(2) DEFAULT '男', hire_date DATE, dept_id INT, phone VARCHAR(20), CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES t_dept(dept_id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_salary ( salary_id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, base_salary DECIMAL(10,2) DEFAULT 0.00, bonus DECIMAL(10,2) DEFAULT 0.00, deduction DECIMAL(10,2) DEFAULT 0.00, pay_date DATE, CONSTRAINT fk_salary_emp FOREIGN KEY (emp_id) REFERENCES t_employee(emp_id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里两个外键用了不同的删除策略,值得记住:t_employee 的 dept_id 删除部门时置空(SET NULL),因为一个部门解散了,员工不能跟着消失;t_salary 的 emp_id 删除员工时连带清掉薪资记录(CASCADE),因为员工都没了薪资记录没意义。这两个策略答辩老师最喜欢问,答上来就说明你真的理解外键设计。

给 t_dept 和 t_employee 补初始数据,否则界面登录进去是空表,看不出效果:

INSERT INTO t_user (username, password, role) VALUES ('admin', '123456', 'admin'); INSERT INTO t_dept (dept_id, dept_name) VALUES (1, '人事部'); INSERT INTO t_dept (dept_id, dept_name) VALUES (2, '技术部'); INSERT INTO t_employee (emp_no, emp_name, gender, hire_date, dept_id, phone) VALUES ('E001', '张三', '男', '2023-06-01', 1, '13800001111');

参数说明:emp_no 是工号也是业务主键,虽然 emp_id 已经自增,但员工在 Excel 和工资单上显示的是工号,所以加了 UNIQUE 约束。hire_date 用 DATE 而不是 DATETIME,因为入职日期不需要时分秒。phone 用 VARCHAR 存,很多新手用 INT 存手机号,手机号超过 2^31 会溢出,这是经典坑。

3. 用 JDBC 连 MySQL 8:驱动加载到 PreparedStatement 的封装

3.1 先写连接工具类:数据库连接池前的标准套路

JDBC 的第一步是拿到数据库连接。建议单独建一个 JDBCUtil 类,把连接信息放到 src 下的 db.properties 文件里,不要硬编码在 Java 代码中。用 IDEA 开发时注意,db.properties 要放在 src 目录下,否则 CLASS_PATH 加载不到。

先看 db.properties:

driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/hrms?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true username=root password=123456

说明一下 url 里这几个参数:useSSL=false 是因为本地开发不需要 SSL 加密,mysql8 默认开 SSL 会报警告。serverTimezone=Asia/Shanghai 必须加,不加会报时区错误 "The server time zone value '�й���ʱ��' is unrecognized" 或类似乱码提示。characterEncoding=utf8 解决中文乱码。allowPublicKeyRetrieval=true 在 mysql8 用 caching_sha2_password 插件时必须加,否则会报 Public Key Retrieval is not allowed,这是连接 MySQL 8 最常见的坑之一。

再看连接工具类:

import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; import java.io.InputStream; public class JDBCUtil { private static String driver; private static String url; private static String username; private static String password; static { try (InputStream in = JDBCUtil.class.getClassLoader() .getResourceAsStream("db.properties")) { Properties props = new Properties(); props.load(in); driver = props.getProperty("driver"); url = props.getProperty("url"); username = props.getProperty("username"); password = props.getProperty("password"); Class.forName(driver); // 加载驱动 } catch (Exception e) { e.printStackTrace(); throw new ExceptionInInitializerError("数据库配置加载失败"); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } }

逻辑说明:静态代码块在类第一次加载时执行,把 db.properties 里的配置读进内存,然后 Class.forName 注册驱动。MySQL 8 以上用 com.mysql.cj.jdbc.Driver,MySQL 5.x 用 com.mysql.jdbc.Driver,两个驱动类名不一样,混用会报 ClassNotFoundException。getConnection 每次调用返回一个新连接,在课程设计这种单机低并发场景完全够用,不需要一开始就上连接池。

3.2 PreparedStatement、结果集与手动事务

JDBC 增删改查的代码风格要统一,否则界面层会越写越乱。下面是一个标准的按工号查询员工方法,全程用 PreparedStatement 而不是 Statement,可以防 SQL 注入,也是答辩会被问到的点:

public static Employee findByNo(String empNo) { String sql = "SELECT emp_id, emp_no, emp_name, gender, hire_date, dept_id, phone " + "FROM t_employee WHERE emp_no = ?"; Employee emp = null; try (Connection conn = JDBCUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, empNo); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { emp = new Employee(); emp.setEmpId(rs.getInt("emp_id")); emp.setEmpNo(rs.getString("emp_no")); emp.setEmpName(rs.getString("emp_name")); emp.setGender(rs.getString("gender")); emp.setHireDate(rs.getDate("hire_date")); emp.setDeptId(rs.getInt("dept_id")); emp.setPhone(rs.getString("phone")); } } } catch (SQLException e) { e.printStackTrace(); } return emp; }

代码里用了 try-with-resources 语法,JDK7 以上支持,这样 Connection、PreparedStatement、ResultSet 都能自动关闭,不用手写 finally。这个写法本身就能规避“连接泄漏”问题,课程设计的代码里如果出现手动 close 但忘了关 ResultSet 的情况,运行久了会报 Too many connections。

增删改的操作套路一样,只是执行 executeUpdate 而不是 executeQuery。注意插入语句要返回自增主键时,可以用 prepareStatement(sql, Statement.RETURN_GENERATED_KEYS),执行后用 rs.getInt(1) 取出自增 id,这个技巧在插入员工后要立刻登记薪资表时很实用。

手动事务是课程设计要求里的重点,通常给你一个场景:“给员工调薪,同时更新薪资表和个人状态”。这种跨表操作必须放在事务里,下面这段是标准写法:

Connection conn = null; try { conn = JDBCUtil.getConnection(); conn.setAutoCommit(false); String sql1 = "UPDATE t_employee SET dept_id = ? WHERE emp_id = ?"; String sql2 = "INSERT INTO t_salary (emp_id, base_salary, pay_date) VALUES (?, ?, NOW())"; try (PreparedStatement ps1 = conn.prepareStatement(sql1); PreparedStatement ps2 = conn.prepareStatement(sql2)) { ps1.setInt(1, 2); // 调到技术部 ps1.setInt(2, 1); // 员工ID为1 ps1.executeUpdate(); ps2.setInt(1, 1); ps2.setBigDecimal(2, new BigDecimal("8500.00")); ps2.executeUpdate(); } conn.commit(); // 两个操作都成功才提交 } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); } finally { if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }

逻辑说明:conn.setAutoCommit(false) 之后,每条 SQL 不会自动提交,必须等到 commit 才生效。中间如果第二条 SQL 失败,会进 catch 分支执行 rollback,第一条的修改也一起撤销。这里有三个参数是调的:setInt 里面的目标部门ID和员工ID,以及 setBigDecimal 里的薪资数。New BigDecimal("8500.00") 不要写成 8500.00 直接传 double,double 转 BigDecimal 会有精度损失。

3.3 把 DAO 层从 Swing 界面层中剥离

课程设计最常见的翻车写法是:在 JButton 的 actionPerformed 里直接写 JDBC 代码。这样能跑,但一个窗口几百行,调试时无法定位到底是界面问题还是 SQL 问题。建议分层:Swing 界面只调用 Service 或 DAO 方法,不出现 Connection 和 PreparedStatement。

我一般会按这个分包结构组织:

src/ db.properties com.hrms.util.JDBCUtil com.hrms.dao.EmployeeDao com.hrms.dao.SalaryDao com.hrms.model.Employee com.hrms.model.Salary com.hrms.ui.LoginFrame com.hrms.ui.MainFrame

model 里的 Employee 类是纯 JavaBean,字段与 t_employee 一一对应;ui 包下的类只做两件事:收集用户输入、调用 DAO 返回结果展示。中间不需要 Service 层,因为课程设计没有复杂业务规则,DAO 直接暴露方法给界面即可。

4. JavaSwing 界面层:登录、员工管理与数据刷新

4.1 登录窗口:一个密码框的细节

登录窗口看起来简单,但有两个细节决定体验。第一,密码框要用 JPasswordField 而不是 JTextField,否则输入密码明文显示,答辩现场很减分。第二,登录按钮的 ActionListener 里要做空判断和角色判断,不能只查 username 和 password 是否匹配。

下面是一个最小登录逻辑:

JButton loginBtn = new JButton("登录"); loginBtn.addActionListener(e -> { String name = usernameField.getText().trim(); String pwd = new String(passwordField.getPassword()).trim(); if (name.isEmpty() || pwd.isEmpty()) { JOptionPane.showMessageDialog(loginFrame, "用户名和密码不能为空"); return; } User u = UserDao.findByUsernameAndPassword(name, pwd); if (u != null) { new MainFrame(u).setVisible(true); loginFrame.dispose(); } else { JOptionPane.showMessageDialog(loginFrame, "用户名或密码错误"); } });

注意 getPassword() 返回的是 char[] 而不是 String,new String 转一下是常规操作。登录成功后把 User 对象传给主窗口,主窗口根据 role 判断是否显示“系统管理”菜单,这是权限控制的简单实现。失败提示不要写“用户名不存在”或“密码错误”,直接统一提示“用户名或密码错误”,避免被猜到账户是否存在,课程设计里也能体现安全意识。

4.2 员工管理界面:JTable 与刷新数据的正确姿势

员工列表用 JTable 展示是主流做法。JTable 本身只负责显示,数据放在 TableModel 里。每次查询后要刷新表格,最简单粗暴也最可靠的方式是重新构建 DefaultTableModel,再 setModel 到表格上。很多新手在同一个 TableModel 里改数据后调 fireTableDataChanged 发现界面不变,原因在于 model 没被 JTable 引用或被引用了但结构已经变更。

刷新表格的标准写法:

public void refreshTable(List<Employee> list) { String[] columns = {"工号", "姓名", "性别", "入职日期", "部门ID", "电话"}; Object[][] data = new Object[list.size()][columns.length]; for (int i = 0; i < list.size(); i++) { Employee e = list.get(i); data[i][0] = e.getEmpNo(); data[i][1] = e.getEmpName(); data[i][2] = e.getGender(); data[i][3] = e.getHireDate(); data[i][4] = e.getDeptId(); data[i][5] = e.getPhone(); } DefaultTableModel model = new DefaultTableModel(data, columns); empTable.setModel(model); // 整体替换 model }

逻辑说明:每次刷新创建新 model,JTable 会重新绘制。这里有两个关键点,第一是日期字段 hire_date 如果没有格式化,表格里会显示成 “2023-06-01” 的字符串形式,但如果你用 rs.getDate 得到 java.sql.Date 再放进 Object[],JTable 默认会显示成 “2023-06-01” 没问题;但如果 getHireDate() 返回的是 java.util.Date,显示出来是 “Thu Jun 01” 这种英文格式,所以 DAO 层最好直接返回 java.sql.Date 或格式化好的字符串。第二是列宽,工号和姓名的列宽不用改,但电话列默认被压缩成省略号,建议设置列宽:empTable.getColumnModel().getColumn(5).setPreferredWidth(120)。

删除员工时要先拿选中行的 emp_id,这里有个坑:表格显示的是工号 emp_no,但删除 SQL 用 emp_id 作条件。正确做法是点击行时从 TableModel 里取工号,再调用 EmployeeDao 按工号查 ID,或者更省事,把 emp_id 用 setUserObject 存在表格对象里,但那个做法容易混淆,课程设计用简单方案即可:在 DAO 里直接提供 deleteByEmpNo 方法。

4.3 ComboBox 显示部门名、提交部门ID:下拉框的典型坑

添加或编辑员工时,部门选择用 JComboBox 展示所有部门。直观思路是“下拉框显示部门名,选中某项,提交时把这个名字存进员工表”,但员工表的外键是 dept_id 不是 dept_name,所以必须建立 显示值→真实值 的映射。

常见做法是维护一个 List ,下拉框里塞 dept_name,选中后按下标从列表里取 dept_id:

private List<Dept> deptList; public void loadDeptData() { deptList = DeptDao.listAll(); DefaultComboBoxModel<String> comboModel = new DefaultComboBoxModel<>(); for (Dept d : deptList) { comboModel.addElement(d.getDeptName()); } deptComboBox.setModel(comboModel); } private int getSelectedDeptId() { int index = deptComboBox.getSelectedIndex(); return deptList.get(index).getDeptId(); }

getSelectedIndex 返回下拉框当前选中项的下标,如果 deptList 和下拉框数据顺序一致,这个取值就是准的。这里务必注意:不要在加载下拉框后又对 comboModel 做 addElement 或 removeElement,那会让列表和 deptList 顺序错位,提交的 dept_id 就张冠李戴了。编辑员工时要在加载下拉框后定位到员工原来的部门:for 循环遍历 deptList,找到匹配的 dept_id,再 setSelectedIndex(i)。

5. 课程设计避坑:环境、编码、表格刷新与事务处理的十次教训

5.1 driver 找不到,ClassNotFound 的三种情境

现象:运行程序第一行就抛 java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver。

原因:最常见的是 mysql-connector-java 的 jar 包没有放进项目的构建路径。很多人在 IDEA 里下载了 jar 包,但只加入了模块依赖,没有复制到项目 lib 目录,导致运行时类加载器找不到驱动类。第二种原因是 MySQL 版本和驱动版本不匹配,比如 MySQL 8 却用了 5.1.x 的驱动,连接时会直接报 “Unable to load authentication plugin 'caching_sha2_password'” 或 ClassNotFound。

解决:用 IDEA 时打开 File → Project Structure → Modules → Dependencies,把 mysql-connector-j 添加进去。另外在项目根目录建 lib 文件夹,把 jar 复制进去再右键 Add as Library,这种物理复制方式避免换机器后依赖丢失。如果版本不对,去 MySQL 官网下载对应版本,MySQL 8 用 connector/j 8.0.33 以上版本。

5.2 中文乱码:三种原因叠加

现象:写入数据库的中文变成问号,或者界面上查出来的中文乱码。

原因:乱码不是单一代码问题,而是三个环节的编码不统一。第一,Java 源文件本身用了 UTF-8 编码,但是 IDEA 全局默认是 GBK;第二,db.properties 里的 url 没有 characterEncoding=utf8 参数;第三,数据库建库时字符集不是 utf8mb4。三个环节只要有一个断了,中文就乱。

解决:在 IDEA 里 Settings → Editor → File Encodings 全部设成 UTF-8;url 里加 characterEncoding=utf8;建库语句用 utf8mb4。排查时按这个顺序用 navicat 或 mysql workbench 依次检查,能在哪一步看到乱码就说明断在哪一步。最隐蔽的是 IDEA 的 properties 文件默认用 ISO-8859-1 读取,db.properties 里的中文乱码就是这个原因,如果配置里没有中文则不受影响。

5.3 时区问题:MySQL 8 特有的拦路虎

现象:启动项目连接数据库时报错 Communications link failure,后面跟着 “The server time zone value 'XXXXXXXX' is unrecognized or represents more than one time zone”。

原因:MySQL 8 默认使用系统时区,如果系统是 CST 这种缩写时区,JDBC 驱动无法解析。这是 MySQL 8 比 MySQL 5.x 多出来的问题,5.x 很少遇到。

解决:url 加 serverTimezone=Asia/Shanghai,这个最省事。不要在 MySQL 端执行 set global time_zone='+08:00',虽然也能解决,但要重启服务端,课设环境里没必要动服务器配置。

5.4 程序卡死:数据库连接被占用没释放

现象:界面点几次查询后,程序越来越卡,最后整个窗口假死,控制台打印 “Too many connections”。

原因:打开连接但没有关闭。典型场景是在 finally 里只关了 PreparedStatement 没关 Connection,或者用了 try-with-resources 但 Connection 是在 try 外面创建的,导致连接泄漏。

解决:用 3.2 节展示的 try-with-resources 模式可以彻底根治。如果是自己手动关闭,记住一个原则:先关 ResultSet,再关 Statement,最后关 Connection,顺序反了会导致 Statement 还持有 ResultSet 时被意外关闭。也可以用自建连接池接管,但课程设计用 try-with-resources 就够了。设计工具类时还可以让 getConnection 每次返回新连接,避免多个窗口共用同一连接导致一个窗口的事务阻塞另一个窗口的查询。

5.5 JTable 死活不刷新:重新 setModel 才是后悔药

现象:查询按钮点了,控制台打出 SQL 查询结果,但按钮上方的 JTable 内容不变。代码里用了 model.fireTableDataChanged() 也没用。

原因:fireTableDataChanged 只适用于原 TableModel 的数据行数没变、内容变了的情况。如果查询结果的行数变了(比如第一次查出 10 条,第二次查出 1 条),原 DefaultTableModel 的行数没同步重构,Swing 不会清掉多余的行。

解决:把表格模型整体替换,也就是 4.2 节的 refreshTable 写法。每次重新 new DefaultTableModel(data, columns),然后 empTable.setModel(model)。这是最稳定、也最好向答辩老师解释的刷新方案。如果不想整体替换,需要在原 model 上 setRowCount(0) 清掉所有行再逐行 addRow,两个方案二选一,不要混用。

5.6 外键约束导致删除失败

现象:删除员工时报 mysqld_exception 或 SQLIntegrityConstraintViolationException,提示 Cannot delete or update a parent row。

原因:t_salary 表里还有该员工的薪资记录,外键约束阻止删除。如果在建表时用了 ON DELETE CASCADE,则不会报错,但很多课程设计里学生漏写了级联规则,导致删不了数据。

解决:删除前先查 t_salary 表有没有该员工的记录,有就先删工资记录再删员工。或者在建表时用 ON DELETE CASCADE 关联外键。推荐方案是:界面层做二次确认弹窗,提示“该员工有薪资记录,删除后薪资记录也会一并删除”,这比直接报红错误体验好很多。

6. 让课设更像生产系统:连接池、事务校验、自测清单

6.1 引入一个轻量连接池:为什么课程设计值得做这一步

如果答辩老师问你“你的 JDBC 方案在生产环境有什么问题”,答案就是每次 getConnection 都新建物理连接太慢。用连接池可以解决,常见做法是引入 HikariCP,它是目前 Java 生态里性能最好的连接池,配置也比 DBCP 简单。

只需要把 JDBCUtil 的获取连接方式改成从 HikariDataSource 拿:

private static HikariDataSource dataSource; static { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/hrms?serverTimezone=Asia/Shanghai&characterEncoding=utf8"); config.setUsername("root"); config.setPassword("123456"); config.setMaximumPoolSize(10); config.setMinimumIdle(2); config.setConnectionTimeout(3000); dataSource = new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }

逻辑说明:maximumPoolSize 设 10 意味着最多同时保持 10 个连接,超过的请求排队等待。minimumIdle 设 2 是池里最少保持 2 个空闲连接,避免刚启动时频繁创建。connectionTimeout 设 3000 毫秒,如果 3 秒拿不到连接就抛异常而不是无限等下去,这能防止死锁。注意引入了 HikariCP 之后,try-with-resources 仍然适用,conn.close() 的实际效果是归还连接到池中,不是真的关闭。

6.2 事务验证:调薪操作必须成对生效

事务部分最容易被忽略的是“验证”步骤。写完调薪功能后,你要故意让第二条 SQL 报错,确认第一条会不会被回滚。测试方法是:在 ps2.executeUpdate() 之前手动写一行 int x = 1 / 0; 制造运行时异常。

运行后查询员工表,如果部门没变,说明回滚生效;如果部门变了而薪资没插入,说明你的事务没启作用,多半是 setAutoCommit(false) 没被调起来,或者连接对象在事务方法里被 Service 层 close 掉了。这个验证视角本质上是在验证“提交、回滚、关闭”三个动作的时序。回滚后连接要恢复 autoCommit 为 true 再还回连接池,否则下一个使用该连接的请求会继续沿用 false 状态,导致默默开启一个长事务。

6.3 交付前的开放验证链路:从界面到数据库的链路

课程设计提交前,我习惯跑三轮验证。第一轮是功能链路:登录 → 添加员工 → 修改员工 → 删除员工 → 查询员工 → 调薪事务 → 退出,每个按钮都点一遍,记录操作结果。第二轮是数据落地验证:停止程序后用 navicat 或 mysql workbench 连库,直接查 t_employee 表,确认界面操作的内容确实写到数据库里,这一步能抓出“运行时不报错但数据根本没写进去”的隐藏 bug。第三轮是重启验证:关闭程序重新启动,看数据是否还保留。如果数据丢了,检查是不是每次启动都执行了 DROP TABLE 或 CREATE TABLE,有些课设为了方便测试把建表语句写在 main 方法里,一启动就重建表,数据全清空,这是交付时最尴尬的事。

最好再让程序支持一个“导出员工列表”到文本文件的功能,不需要引入 POI 依赖,用 BufferedWriter 写 CSV 格式就行。这不光是为了展示功能,更是为了让答辩老师看到你考虑过数据落地。

6.4 一个教训:不要用 Thread.sleep(1000) 去等数据库连接

曾经为了等数据库连接成功后弹提示,在代码里写过 Thread.sleep(1000)。运行结果是在电脑通讯录里折腾了半天。数据库连接是同步的,拿不到连接会直接抛异常或阻塞在 getConnection,根本不需要 sleep。如果启动时连接失败,显示一个明确的错误提示框比假装重试一整秒有意义得多。

我的操作习惯是每写完一个功能就单独跑一次,不把四个功能全写完再统一调试。比如写完登录就去测试连接、写员工列表就去查 SQL、写事务前就先跑一下回滚验证。这样出现问题能立刻定位到具体模块,而不是在几百行代码里猜。这个顺序对只给两个月做课设的同学来说,是最快拿到一个能跑通项目的路径。希望这一套 JavaSwing + MySQL + JDBC 的落地拆解能帮你在数据库课程设计里少一点玄学、多一点可控的确定性。

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

返回列表