
简介一套基于Java Swing与MySQL数据库的课程设计人事管理系统完整源码包专门面向需要完成数据库课程设计或练习JDBC开发的Java初学者。系统自带图形化操作界面通过JDBC实现与MySQL的交互覆盖人事管理中的员工信息维护、考勤记录、薪资查询等典型功能模块能够直观展示桌面程序与数据库协同工作的完整链路。资源压缩包共包含24个文件其中11个java源文件用于核心业务逻辑与界面实现12个png图片为系统运行界面截图另有1个sql文件提供建库建表脚本整体大小仅143KB便于快速下载与部署。根据资源描述运行中可能遇到的报错多为数据库版本差异导致的基础连接或语法问题读者可结合本地上手自行解决。目前已有3878人学习或下载过该资源对于希望借鉴完整项目结构、参考界面设计或快速搭建人事管理演示系统的学习者来说是一份轻量且实用的参考资料。1. 数据库课程设计选人事管理系统为什么绕不开 javaswing mysql jdbc每到期末Java 课程设计题目里出现频率最高的就是人事管理系统。javaswing 负责把数据库里的数据画成窗口mysql 存数据jdbc 在两者之间跑 SQL这个组合几乎成了数据库课程设计的标准答案。理由很直白Swing 是 JDK 自带的图形界面库不需要额外装前端环境MySQL 免费且安装教程遍地都是JDBC 是 Java 操作数据库的原生接口老师一眼就能看出你懂没懂原理。整个链路没有任何黑匣子从建库到跑通最远不过一个下午。这套组合适合两类人一类是被课程设计逼着交作业的学生目标是可运行、有界面、答得上答辩另一类是刚入职想快速做个内部小工具练手的初级开发。系统要解决的核心问题也很简单——员工的增删改查、按条件找人、部门与员工的关系维护。别小看这个范围真把它做到不翻车涉及表结构设计、JDBC 资源管理、Swing 事件线程、中文乱码处理每一个都是从业者日常要面对的东西。这篇就按我实际做这类系统的顺序讲先立数据层再做界面最后集中说坑。2. 建库建表与 JDBC 连接先把数据层独立跑通2.1 人事管理系统的表结构设计五张表和一对外键课程设计的人事管理系统表数量不用多但关系要能讲出东西。我一般建议设计五张表用户表登录用、部门表、员工表、工资表、考勤表。其中员工表和部门表通过部门编号建立外键关系员工表和工资表、考勤表通过员工编号关联。这样的结构在答辩时能讲出「一对多」「一对一」两个关系而且 SQL 查询里自然会用到 JOIN比单表增删改查有说服力得多。CREATE DATABASE personnel DEFAULT CHARACTER SET utf8mb4; USE personnel; CREATE TABLE dept ( dept_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL UNIQUE, location VARCHAR(100) ); CREATE TABLE employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, gender CHAR(2), age INT, dept_id INT, hire_date DATE, phone VARCHAR(20), FOREIGN KEY (dept_id) REFERENCES dept(dept_id) ); CREATE TABLE user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL );建库时把DEFAULT CHARACTER SET utf8mb4写进建库语句是后面少折腾中文乱码的关键。utf8mb4 是 MySQL 8 的默认字符集能存emoji和生僻字兼容性比老 utf8 好。员工表里的emp_no是业务编号比如EMP001它和自增主键emp_id分开是为了以后系统对接打卡机之类的外部数据时业务编号不变、主键随便改也不影响关联。外键约束我建议加上。有人觉得外键影响插入效率想省掉但课程设计阶段外键能在你写错部门编号时立刻报错帮你守住数据一致性答辩时也能理直气壮说「数据库层面做了完整性约束」。2.2 MySQL 8 的驱动与连接串时区、SSL、编码三个必调参数表建好之后下一步不是写界面而是先写一个能跑通的 JDBC 连接。这步卡住的人最多十有八九是驱动版本和连接串参数的问题。我用的依赖是mysql-connector-java8.0.x 或com.mysql:mysql-connector-j8.x后者是 MySQL 官方从 8.0.31 开始用的新坐标。如果你是 Maven 项目直接引新坐标如果是手动把 jar 包放进lib目录就去 MySQL 官网下载对应版本。import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBConnection { public static void main(String[] args) { String url jdbc:mysql://localhost:3306/personnel ?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; String user root; String password 你的密码; try { Class.forName(com.mysql.cj.jdbc.Driver); Connection conn DriverManager.getConnection(url, user, password); System.out.println(连接成功 conn.getMetaData().getDatabaseProductName()); conn.close(); } catch (ClassNotFoundException e) { System.err.println(驱动类没找到检查 jar 包是否引入); } catch (SQLException e) { System.err.println(连接失败错误码 e.getErrorCode()); e.printStackTrace(); } } }连接串里三个参数是必调的。useSSLfalse是关闭 SSL 加密本地开发库没必要做 TLS 握手少了它 MySQL 8 会在连接日志里刷大量 warning个别环境还会直接握手失败serverTimezoneAsia/Shanghai是告诉驱动服务器时区不写的话常见报错是The server time zone value йʱ is unrecognized一串乱码看得人头皮发麻characterEncodingutf8指定客户端编码保证中文写入前编码统一。Class.forName在 8.x 驱动里其实可以省略但写上没坏处能让新手看到驱动加载的完整链路。2.3 第一个 JDBC CRUD用 PreparedStatement 写员工插入连接能通之后立刻写第一个增删改查。这一步不求花哨但要用对 API。很多人图省事用Statement拼 SQL一旦员工姓名里出现单引号SQL 直接语法错误更别提 SQL 注入这在课程设计答辩里是被问倒的高频点。所以从一开始就写PreparedStatement这是 JDBC 里最值得养成的习惯。public int insertEmployee(Connection conn, Employee emp) throws SQLException { String sql INSERT INTO employee (emp_no, name, gender, age, dept_id, hire_date, phone) VALUES (?, ?, ?, ?, ?, ?, ?); try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, emp.getEmpNo()); ps.setString(2, emp.getName()); ps.setString(3, emp.getGender()); ps.setInt(4, emp.getAge()); ps.setInt(5, emp.getDeptId()); ps.setDate(6, emp.getHireDate() ! null ? new java.sql.Date(emp.getHireDate().getTime()) : null); ps.setString(7, emp.getPhone()); return ps.executeUpdate(); } }这里try-with-resources是关键PreparedStatement 用完自动关闭不用手动写finally里那一坨close()。setDate那行需要特别说明实体类里hireDate通常用java.util.Date但 JDBC 的setDate只认java.sql.Date所以要做一次转换不然编译报错。executeUpdate返回的是受影响行数插入成功返回 1业务代码可以根据返回值判断操作成败。3. 用 JDBC 封装数据访问层连接管理、ORM 映射和 DAO 结构3.1 连接池还是单连接课程设计尺度下的选择到这一步要不要上数据库连接池是很多人纠结的第一个问题。常见做法是课程设计不上连接池手写一个工具类管理连接。理由很简单——连接池的配置和使用是另一个层面的知识如果只是为了交作业引入 HikariCP 会让代码复杂度陡增答辩时老师追问「连接池原理是什么」反而容易翻车。手写单连接管理足够覆盖课设需求而且能讲清楚 JDBC 本身。但如果你心里清楚自己未来要写真正的项目连接池是躲不掉的。我个人的建议是课设阶段先手写等系统跑通了再花半小时把连接获取改成 HikariCP体会两种方式的差别。常见做法是public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/personnel ?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }static块保证驱动只加载一次避免每次getConnection重复加载。把连接串、用户名、密码收拢到一个类里后续换库改密码只改这一个文件这也是课程设计里「代码整洁」的加分项。3.2 写一个 DBUtil 工具类关闭资源的正确姿势上面只写了获取连接还缺一个资源关闭的标准姿势。写 JDBC 的老手都知道Connection、Statement、ResultSet这三个东西哪个没关都会把数据库连接数占满轻则应用变慢重则直接报Too many connections。连接没关这个问题是数据库开发最常见的翻车原因网上搜「mysql too many connections」能看到大量血泪帖。public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }关闭顺序不能乱先关ResultSet再关Statement最后关Connection。如果先关连接Statement 和 ResultSet 的关闭行为就不确定了底层 socket 可能没完全释放。close方法本身抛SQLException但在关闭场景下这个异常通常不影响业务打印出来就好不要用它打断主流程。3.3 DAO 与实体类ResultSet 到对象的三步映射表、工具类都有了还差数据访问层。所谓 DAOData Access Object就是把 SQL 和业务代码隔离开界面层调 DAO 方法拿到对象不直接碰ResultSet。这个分层在课设里不是必须的但写了之后界面代码会清爽非常多。public class EmployeeDao { public ListEmployee findByName(String keyword) { String sql SELECT e.emp_id, e.emp_no, e.name, e.gender, e.age, e.dept_id, d.dept_name, e.hire_date, e.phone FROM employee e LEFT JOIN dept d ON e.dept_id d.dept_id WHERE e.name LIKE ?; ListEmployee list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Employee emp new Employee(); emp.setEmpId(rs.getInt(emp_id)); emp.setEmpNo(rs.getString(emp_no)); emp.setName(rs.getString(name)); emp.setGender(rs.getString(gender)); emp.setAge(rs.getInt(age)); emp.setDeptId(rs.getInt(dept_id)); emp.setDeptName(rs.getString(dept_name)); emp.setHireDate(rs.getDate(hire_date)); emp.setPhone(rs.getString(phone)); list.add(emp); } } } catch (SQLException e) { e.printStackTrace(); } return list; } }这段代码里有三个常见的边界细节。第一个是LEFT JOIN而不是INNER JOIN这样即使员工还没分配部门也能查出员工记录只是dept_name为null界面展示时补个「未分配」就行。第二个是LIKE ?配合setString(1, % keyword %)通配符在参数里而不在 SQL 里保持了PreparedStatement的防注入特性。第三个是try-with-resources里连接和语句并发关闭Connection的关闭会释放底层 socket不需要额外再写DBUtil.close。ResultSet转对象的过程中我建议所有getXxx都用列名而不是列序号。列名可读性强而且如果 SQL 字段顺序调整用序号取值就全乱了。4. javaswing 界面和 JDBC 数据绑定从 JTable 到增删改查4.1 JTable 的 TableModel 刷新为什么查询后表格不更新Swing 里最常用的数据展示控件是JTable但新手最容易犯的错是查完数据库后直接操作表格控件结果界面上什么都没变。原因在于JTable本身不存数据数据都在TableModel里你往模型里塞数据表格才会刷新。private DefaultTableModel model; private JTable table; private void refreshTable(String keyword) { ListEmployee list new EmployeeDao().findByName(keyword); model.setRowCount(0); // 清空旧数据 for (Employee emp : list) { model.addRow(new Object[]{ emp.getEmpNo(), emp.getName(), emp.getGender(), emp.getAge(), emp.getDeptName(), emp.getHireDate(), emp.getPhone() }); } }setRowCount(0)是把模型里的旧行全部清掉再逐行addRow。这一步在逻辑上相当于「重新查询 → 填充数据 → 表格自动重绘」。很多教程会教你先table.updateUI()这其实是走弯路。更新TableModel才是正确路径因为模型的变化会触发TableModelListener表格自己会刷新。4.2 按钮事件与线程阻塞Swing 的事件分发线程界面上的「查询」「新增」「删除」按钮都要挂事件监听这些监听器跑在 Swing 的事件分发线程EDT上。如果你在事件里执行一个耗时 SQL界面会卡死鼠标点了没反应看起来像程序崩了。这就是为什么查询按钮的代码里要把数据库访问单独处理。btnSearch.addActionListener(e - { String keyword txtKeyword.getText().trim(); // 耗时操作放到新线程避免界面假死 new Thread(() - { ListEmployee list new EmployeeDao().findByName(keyword); SwingUtilities.invokeLater(() - refreshTable(keyword)); }).start(); });注意这里有两个坑。第一个是keyword在 Lambda 里被引用必须是 effectively final所以先trim()存到局部变量。第二个是刷新表格的操作要放回事件线程用SwingUtilities.invokeLater包裹这是 Swing 的线程规则数据查询可以在后台线程但 UI 更新必须回到 EDT。简单系统直接在主事件里跑也死不了但养成这个习惯后以后接触真实项目不会因为线程问题踩大坑。4.3 组合查询在 WHERE 上拼条件而不是拼 SQL人事管理系统几乎都带「按部门 按性别 按入职时间」这种组合查询。常见的做法是动态拼接 WHERE 子句但很多人一拼就把PreparedStatement扔掉了直接用字符串拼接11这是很烂的写法——既难读又有注入风险。我一般这么处理先固定WHERE 11保证后续条件前都有AND然后按需拼条件片段参数全部用?收集到 List 里最后循环setObject。public ListEmployee search(Integer deptId, String gender, String keyword) { StringBuilder sql new StringBuilder( SELECT e.*, d.dept_name FROM employee e LEFT JOIN dept d ON e.dept_id d.dept_id WHERE 11 ); ListObject params new ArrayList(); if (deptId ! null deptId 0) { sql.append(AND e.dept_id ? ); params.add(deptId); } if (gender ! null !gender.isEmpty()) { sql.append(AND e.gender ? ); params.add(gender); } if (keyword ! null !keyword.isEmpty()) { sql.append(AND (e.name LIKE ? OR e.emp_no LIKE ?) ); params.add(% keyword %); params.add(% keyword %); } try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql.toString())) { for (int i 0; i params.size(); i) { ps.setObject(i 1, params.get(i)); } ... } }WHERE 11从性能角度讲MySQL 优化器会直接忽略这个恒真条件不会产生额外开销。从代码角度讲它让每个条件都能用AND开头省去判断「是不是第一个条件」的逻辑。setObject会自动匹配类型比逐个setString或setInt精简很多。5. 课程设计避坑指南数据库连不上、中文乱码、驱动冲突与答辩追问5.1 error 2002 与连接被拒MySQL 服务没起来还是端口被占课程设计最常见的第一道坎就是数据库连不上。在 Linux 上装好 MySQL 后经常遇到ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。这个报错有 90% 是服务没启动而不是密码错误。现象是客户端连不上报错提示 socket 文件找不到。先检查服务状态再检查端口监听。解决步骤是systemctl status mysql看服务状态如果显示 inactive就systemctl start mysql启动。启动后ss -tlnp | grep 3306看端口是否监听。如果服务起了但端口没监听多半是配置文件里skip-networking被打开了注释掉这行再重启。Windows 上的表现不同通常是com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure。先用netstat -ano | findstr 3306看端口再用sc query mysql查服务。MySQL 8 安装后默认服务名不一定是 mysql可能是MySQL80用管理员身份打开命令行跑net start MySQL80。我见过最多的情况是装的时候选了不启动服务第二次开机就忘了开界面报错各种看不懂实际就是一句话的事。5.2 中文乱码连接串、表编码、IDE 三处对齐中文乱码是这个系统里绕不开的坑。现象是界面显示「??」或者一串问号或者往数据库存进去再读出来就变成了乱码。很多人以为只是连接串加个characterEncodingutf8就完了实际上要三处对齐。第一处是数据库表本身建库时有没有指定utf8mb4。如果建的是默认latin1后面改就很麻烦。第二处是 JDBC 连接串必须写characterEncodingutf8这个前面说过了。第三处是 IDE 的文件编码IDEA 里默认file.encoding可能不是 UTF-8导致 Java 源码里的中文字符在被 javac 编译时已经变成了乱码。第三种情况是血泪经验。解决方式是在pom.xml里显式指定编译编码properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties如果不用 Maven就在 IDEA 的 Settings 里搜File Encodings把 Global Encoding、Project Encoding、Default encoding for properties files 全部设为 UTF-8然后重建项目。注意最后一步改动后要重新Build Project否则编译产物里还是旧的乱码字节。这个坑往往在把项目从一台电脑拷到另一台机器时爆发两台机器的默认编码不一样代码里的中文注释和字符串全花了。5.3 驱动版本冲突与 Maven 依赖下载失败用 IDEA 建 Maven 项目时拉取mysql-connector-j依赖偶尔会报Cannot download from Maven Central或Download from Maven failed。这个报错提示了依赖源连接失败但真实原因可能是 IDEA 里 Maven 配置的镜像仓库访问慢或者被墙了。现象是 IDEA 右下角提示依赖下载失败代码里import com.mysql.cj.jdbc.Driver一直报红。解决方式分三步第一步查本机 Maven 的settings.xml确认mirror配置是否指向了可用镜像第二步在 IDEA 的Build Tools - Maven里勾选Always update snapshots再点刷新按钮重新拉取第三步如果还不行直接到 MySQL 官方下载页把mysql-connector-j-8.x.x.jar手动下载放到项目lib目录然后File - Project Structure - Libraries里添加这个 jar。手动加 jar 是我推荐课设阶段最稳的兜底方案不依赖网络也不会因为仓库配置问题浪费两个小时。但你要注意jar 包必须和 JDK 版本兼容MySQL Connector/J 8.0.x 支持 JDK 8 及以上够用。5.4 答辩追问外键、事务、注入怎么答课程设计不只看出不出得了界面答辩提问才决定分数。最常被问的四个问题提前准备好答案就不会慌。第一个是「为什么用外键」。答外键让数据库层面保证部门编号必须真实存在防止应用层忘记校验导致孤儿数据。第二个是「事务你怎么理解」。答一个业务操作要么全部成功要么全部失败比如入职操作包含插入员工表和更新部门人数两步要在一个事务里。第三个是「PreparedStatement 和 Statement 的区别」。答前者预编译、防止 SQL 注入、处理特殊字符更安全后者只能拼接 SQL。第四个是「索引你建了吗」。这个可以诚实一点课程设计的数据量不大索引优化不明显但我知道emp_no这种频繁查询的字段可以建唯一索引。5.5 Swing 界面显示不发光的规律repaint 与 invokeLater还有一个困扰很多人的现象窗口弹出来了但是控件全空白或者表格数据更新后界面没有重绘。原因是 Swing 的美观是在 EDT 上按事件队列处理的如果在非 EDT 线程里改了模型Swing 不保证立刻重绘。大家容易出现这个问题的操作是在ActionListener里调model.addRow后数据不显示其实是因为你在监听器之外又包了别的线程操作。这个问题的标准解法就是前面说的所有 UI 操作放到SwingUtilities.invokeLater里数据模型更新也走 EDT。另外首次显示前发生了数据填充比如构造函数里查库建议先frame.setVisible(true)再填充数据否则某些 LookAndFeel 会因为在窗口未显示时绘制而出现空白面板。这条也是我同事趟出来的属于典型的「怎么写都对但看起来像坏了」。6. 进阶让它从「课设」变成「小系统」——事务、级联联动与备份课设能跑通增删改查已经及格但如果你想拿高分或者把它作为简历上的项目还需要补三块事务控制、界面联动和数据库备份恢复。这三块是真实系统的基本盘。事务这块用connection.setAutoCommit(false)控制。典型场景是「入职」操作插入员工表的同时部门表的人数统计字段要加一。如果两步之间崩了数据就不一致了。代码结构是先关掉自动提交执行两条 SQL全部成功后commit()任一步失败就rollback()最后在finally里恢复自动提交并关闭连接。这个逻辑在答辩时一讲效果和单纯增删改查完全不一样。public boolean hireEmployee(Employee emp, int deptId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 insertEmployee(conn, emp); updateDeptCount(conn, deptId, 1); conn.commit(); return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { try { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }界面联动说的是「选部门 → 员工列表联动更新」这类交互。做法是在部门下拉框的ItemListener里触发员工表的按部门查询不要等到用户点了「查询」才去查。这样体验更接近真实系统代码也只是把refreshTable换成searchByDept而已。如果表之间有关联比如查出的员工列表双击要弹出工资记录需要保证员工选中时拿到的是emp_id而不是界面上显示的emp_no可以在TableModel里隐藏一列emp_id取值时先拿行号再拿隐藏列。备份恢复属于「关键时刻救命」的功能。课程设计里不需要做增量备份但做完一个阶段把数据导出来是防止自己手误删表的后悔药。命令行下就是两条mysqldump -u root -p personnel personnel_backup.sql mysql -u root -p personnel personnel_backup.sql导出的.sql文件要看一下编码用UTF-8打开确认没有乱码。如果数据里有新插入的 emoji 字符而备份文件头部没有SET NAMES utf8mb4恢复时也会丢字符。恢复前最好先DROP DATABASE再重新建库避免旧表结构和新表结构不一致导致导入失败。我个人做项目的习惯是每写完一个完整功能模块先导一次备份改坏了大不了重来。这个习惯在课设后期特别管用——我在调密码加密逻辑时曾经把 user 表整成了一条数据都查不出来如果没有备份那晚就得通宵了。希望这篇能帮你少绕几个弯把人事管理系统一次跑通。本文还有配套的精品资源点击获取