简介:这份资源是一套基于 Java Swing 开发的桌面人事管理系统源码工程,面向正在学习 Java GUI 编程、需要完成课程设计或希望积累桌面应用实战经验的学生与开发者,可用于理解界面组件组织、事件响应与数据持久化的完整实现思路。压缩包共 36 个文件,以 16 个 java 源文件和 16 个 class 编译文件为主体,另含工程配置与依赖 jar,整体约 771KB,体积轻量便于本地导入 IDE 直接运行调试。系统围绕员工信息录入、条件查询、编辑删除以及 CSV 等格式的数据导入导出展开,涉及 JFrame、JTable、JButton、JTextField、JComboBox、JFileChooser 等常用组件,并体现事件监听、MVC 分层、JDBC 数据库连接与异常提示等知识点。目前已有 647 人学习下载,适合作为 Swing 入门到综合练习的参考案例,对照源码可快速梳理界面与业务逻辑的对应关系。
1. 从一份“跑不起来”的课设说起:Java Swing 人事管理系统到底该怎么搭
每年到了课程设计交付季,总有人拿着一份 Java Swing 人事管理系统的源码来找我,说界面能打开,但一加员工就报错,一查考勤就卡死,数据库里还全是重复记录。问题往往不在 Swing 本身,而在于一开始就没把“人事管理”这件事拆清楚:员工档案、部门岗位、考勤记录、薪资核算这几块数据怎么关联,界面层和业务层怎么分工,数据库连接什么时候开什么时候关。这套方案要解决的就是把 Java Swing 人事管理系统从“能演示”做到“能交付”,适合正在做毕设或课设的学生照着搭结构,也适合需要快速出一版桌面端人事工具的从业者参考。下面按需求、设计、实现、避坑、验收的顺序,把每个模块的职责和关键代码讲透。
2. 需求与总体设计:四个业务模块和三层结构的职责划分
动手写界面之前,先把人事管理系统的业务边界划出来。一个合格的人事管理系统通常围绕四件事转:员工信息维护、部门与岗位管理、考勤记录、薪资计算。这四块不是并列的,员工是主数据,部门和岗位是员工身上的属性,考勤和薪资是挂在员工身上的流水。很多课设翻车就翻在这里——把考勤表设计成独立于员工表的存在,结果查某个人的月度考勤时要写一堆关联,还容易漏数据。
2.1 业务需求拆解:谁在用、用什么、数据从哪来
从使用者角度看,这套系统一般有两类角色:人事专员和普通员工。人事专员负责录入和维护员工档案、调整部门岗位、导入或补录考勤、生成月度薪资;普通员工只查询自己的信息和考勤。课设阶段不必做复杂的权限体系,但至少要在登录后区分角色,菜单项按角色显示,这是答辩时容易被问到的点。
数据来源要提前想清楚。员工信息是手工录入为主,考勤数据在真实场景里来自打卡机导出,课设里通常用 Excel 或 CSV 模拟导入,薪资则由基本工资、岗位津贴、考勤扣款按规则算出来。把“数据从哪来、经过谁、存到哪”这条链路写进需求文档,后面设计表结构时就不会拍脑袋。
功能清单建议按模块列,每个模块写清楚增删改查的范围。比如员工管理要支持按姓名、部门、在职状态组合查询;考勤管理要支持按月查看和补录;薪资管理要能按月份批量生成并导出。范围写死,开发时就不会无限加需求。
2.2 三层结构设计:界面层、业务层、数据访问层怎么切
Swing 项目最容易写成“一个 JFrame 里塞几百行事件代码”,改一个字段要翻三个文件。我的做法是强制分三层:界面层只负责控件布局和事件转发,业务层处理校验和计算规则,数据访问层只管 SQL 和连接。三层之间用接口或普通类调用,不跨层访问。
界面层按模块拆成独立的 Panel,主窗口用 JTabbedPane 或左侧菜单切换。每个 Panel 持有对应的业务服务对象,按钮事件里只做“取控件值 → 调服务 → 刷新表格”三件事。业务层放 EmployeeService、AttendanceService、SalaryService 这类类,负责非空校验、工号唯一性检查、薪资公式计算。数据访问层用 EmployeeDao 这类类封装 JDBC,统一从连接池或工具类拿连接。
这样切的好处是答辩时能讲清楚每层的职责,出问题时定位也快:界面不刷新查 Panel,数据不对查 Service,SQL 报错查 Dao。下面这张表是三层职责的对照,写设计文档时可以直接参考。
| 层次 | 典型类 | 职责 | 不该做的事 |
|---|---|---|---|
| 界面层 | MainFrame、EmployeePanel | 布局、事件绑定、表格刷新 | 写 SQL、算薪资 |
| 业务层 | EmployeeService | 校验、规则计算、事务编排 | 直接操作 Swing 控件 |
| 数据访问层 | EmployeeDao | JDBC 连接、SQL 执行、结果映射 | 弹窗提示、业务判断 |
2.3 数据库表设计:员工、部门、考勤、薪资的关联关系
表结构是这套系统的地基。核心四张表:employee(员工)、department(部门)、attendance(考勤)、salary(薪资)。employee 里存 department_id 作为外键,attendance 和 salary 都存 employee_id 和月份字段。工号、身份证号这类字段加唯一约束,避免重复录入。
-- 员工表:主数据,部门用外键关联 CREATE TABLE employee ( id INT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL UNIQUE, -- 工号,业务唯一键 name VARCHAR(50) NOT NULL, gender CHAR(1), phone VARCHAR(20), department_id INT, position VARCHAR(50), hire_date DATE, status TINYINT DEFAULT 1, -- 1在职 0离职 FOREIGN KEY (department_id) REFERENCES department(id) ); -- 考勤表:按员工+月份唯一,防止重复导入 CREATE TABLE attendance ( id INT PRIMARY KEY AUTO_INCREMENT, employee_id INT NOT NULL, work_month CHAR(7) NOT NULL, -- 格式 2024-06 work_days INT DEFAULT 0, late_count INT DEFAULT 0, leave_days INT DEFAULT 0, UNIQUE KEY uk_emp_month (employee_id, work_month), FOREIGN KEY (employee_id) REFERENCES employee(id) );建表时有两个细节值得强调。一是考勤表的uk_emp_month唯一索引,它能在数据库层面挡住重复导入,比在代码里查一遍再插更可靠。二是 status 用 TINYINT 而不是直接删记录,离职员工的历史考勤和薪资还要能查,物理删除会让报表断档。薪资表同理,存 employee_id、月份、基本工资、扣款、实发,唯一键同样是员工加月份。
3. 核心模块实现:从登录鉴权到薪资计算的落地代码
总体设计定下来之后,逐个模块落地。这一章挑四个最能体现工程质量的点:登录与角色、员工档案的增删改查、考勤导入的批量处理、薪资的规则计算。每个点给关键代码和参数说明,照着改字段名就能用。
3.1 登录与角色鉴权:一个方法挡住越权菜单
登录窗口单独做一个 LoginFrame,验证通过后把当前用户对象传给主窗口,主窗口根据角色决定显示哪些菜单。用户表里存 role 字段,0 表示管理员,1 表示普通员工。鉴权不要只做在界面上,业务层的方法入口也要判断,否则有人绕过界面直接调服务就出问题。
// 登录校验:返回用户对象,失败返回 null public User login(String username, String password) { if (username == null || username.trim().isEmpty() || password == null || password.isEmpty()) { return null; // 空值直接拒绝,不查库 } String sql = "SELECT id, username, role FROM sys_user " + "WHERE username = ? AND password = ?"; try (Connection conn = DbUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username.trim()); ps.setString(2, Md5Util.encode(password)); // 存库用摘要,不存明文 try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { return new User(rs.getInt("id"), rs.getString("username"), rs.getInt("role")); } } } catch (SQLException e) { e.printStackTrace(); // 课设可打日志,生产要接日志框架 } return null; }这段代码有三个设计点。密码用 MD5 摘要存库,虽然 MD5 在安全上不算强,但课设层面至少做到不存明文,答辩时能讲出“为什么不能明文存”。用 PreparedStatement 而不是字符串拼接,挡住 SQL 注入,这是基本要求。try-with-resources 保证连接和语句一定关闭,Swing 项目里连接泄漏是导致“用一会儿就卡”的常见原因。
主窗口拿到 User 后,在初始化菜单时判断user.getRole() == 0才添加“用户管理”和“薪资生成”菜单。业务层的敏感方法里再查一次角色,形成双保险。
3.2 员工档案管理:表格绑定与组合条件查询
员工管理界面用 JTable 展示列表,上方放查询条件,下方放增删改按钮。表格数据用 DefaultTableModel,查询结果通过 TableModel 刷新。组合查询的 SQL 要动态拼条件,但值必须走占位符。
// 按姓名、部门、状态组合查询,条件为空则不加 public List<Employee> query(String name, Integer deptId, Integer status) { StringBuilder sql = new StringBuilder( "SELECT e.*, d.dept_name FROM employee e " + "LEFT JOIN department d ON e.department_id = d.id WHERE 1=1"); List<Object> params = new ArrayList<>(); if (name != null && !name.trim().isEmpty()) { sql.append(" AND e.name LIKE ?"); params.add("%" + name.trim() + "%"); } if (deptId != null && deptId > 0) { sql.append(" AND e.department_id = ?"); params.add(deptId); } if (status != null) { sql.append(" AND e.status = ?"); params.add(status); } sql.append(" ORDER BY e.emp_no"); // 执行查询并把 ResultSet 映射成 Employee 列表,略去样板代码 return jdbcTemplate.query(sql.toString(), params, Employee.class); }WHERE 1=1是动态拼接的常用写法,后面每个条件都以 AND 开头,逻辑清晰。LIKE 查询的百分号加在参数值里而不是 SQL 字符串里,配合占位符使用。部门用 LEFT JOIN,即使员工没填部门也能查出来,避免内连接把脏数据过滤掉导致“明明有这个人却查不到”。
界面层在“保存”按钮里先调 Service 的校验方法,检查工号非空、手机号格式、工号是否重复,全部通过再调 Dao 插入,插入成功后重新执行一次当前查询刷新表格。删除按钮弹确认框,确认后把 status 置 0 而不是物理删除。
3.3 考勤批量导入:事务控制与重复数据拦截
考勤数据量大,逐条插入慢且容易半途失败。做法是解析文件后在一个事务里批量插入,任何一条失败整体回滚。配合前面建的唯一索引,重复月份的数据会抛异常,捕获后提示“该月数据已存在”。
// 批量导入考勤:同一事务,失败整体回滚 public int importAttendance(List<Attendance> list) throws SQLException { String sql = "INSERT INTO attendance " + "(employee_id, work_month, work_days, late_count, leave_days) " + "VALUES (?, ?, ?, ?, ?)"; Connection conn = null; try { conn = DbUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,开启事务 try (PreparedStatement ps = conn.prepareStatement(sql)) { for (Attendance a : list) { ps.setInt(1, a.getEmployeeId()); ps.setString(2, a.getWorkMonth()); ps.setInt(3, a.getWorkDays()); ps.setInt(4, a.getLateCount()); ps.setInt(5, a.getLeaveDays()); ps.addBatch(); // 加入批次,不立即执行 } ps.executeBatch(); // 一次性提交批次 } conn.commit(); return list.size(); } catch (SQLException e) { if (conn != null) conn.rollback(); // 任一条失败,全部回滚 throw e; } finally { if (conn != null) { conn.setAutoCommit(true); conn.close(); } } }addBatch 和 executeBatch 把多条插入合并成一次网络往返,数据量大时差别明显。事务边界设在整批数据上,要么全进要么全不进,不会出现“导了一半”的脏状态。捕获异常后回滚再抛出,由界面层统一弹提示。注意 finally 里恢复自动提交再关闭连接,连接归还池子时状态是干净的。
3.4 薪资计算:规则集中在一处,别散落在界面里
薪资 = 基本工资 + 岗位津贴 - 考勤扣款。扣款规则通常是迟到一次扣固定金额、请假按天扣。规则一定要写在 Service 里,界面只传员工和月份,拿回结果展示。这样规则调整时只改一个地方。
// 按员工和月份计算薪资,规则集中在此方法 public Salary calcSalary(int employeeId, String month) { Employee emp = employeeDao.findById(employeeId); Attendance att = attendanceDao.findByEmpAndMonth(employeeId, month); if (emp == null || att == null) { throw new IllegalStateException("员工或考勤数据缺失,无法计算"); } BigDecimal base = emp.getBaseSalary(); // 基本工资 BigDecimal allowance = emp.getAllowance(); // 岗位津贴 BigDecimal lateDeduct = BigDecimal.valueOf(att.getLateCount()) .multiply(new BigDecimal("50")); // 迟到每次50 BigDecimal leaveDeduct = BigDecimal.valueOf(att.getLeaveDays()) .multiply(base.divide(new BigDecimal("21.75"), 2, RoundingMode.HALF_UP)); BigDecimal actual = base.add(allowance).subtract(lateDeduct).subtract(leaveDeduct); if (actual.compareTo(BigDecimal.ZERO) < 0) actual = BigDecimal.ZERO; // 不倒扣 return new Salary(employeeId, month, base, allowance, lateDeduct.add(leaveDeduct), actual); }金额一律用 BigDecimal,不用 double,浮点误差在薪资场景是硬伤。日薪按 21.75 天折算,这是常见的月计薪天数口径,写进注释方便答辩解释。实发工资兜底不小于零,避免极端考勤把工资算成负数。计算前先校验员工和考勤都存在,缺数据直接抛异常而不是静默返回零,问题早暴露。
4. 避坑与排查:Swing 人事系统最容易翻车的五个点
这套系统写下来,真正花时间的不是功能本身,而是几个反复出现的坑。下面五条按“现象 → 原因 → 解决”整理,都是我在类似项目里踩过的。
4.1 界面卡死无响应
现象:点查询或导入后窗口白屏,鼠标转圈,过一会儿才恢复。原因:数据库操作或文件解析直接写在按钮事件里,跑在事件分发线程(EDT)上,阻塞了界面刷新。解决:耗时操作放到 SwingWorker 的 doInBackground 里执行,done 方法里再更新表格和按钮状态。查询超过几百毫秒就该这么改,别等卡了才想起来。
4.2 表格数据与数据库不一致
现象:新增一条员工后表格没变化,或者删了之后刷新还在。原因:界面层改了内存里的 TableModel 却没重新查库,或者查了库但没重置模型。解决:统一成“操作成功 → 重新执行当前查询 → setModel 新数据”的流程,不在本地手动增删行。查询条件要保存下来,刷新时沿用,否则一刷新就回到全量列表。
4.3 连接泄漏导致越用越慢
现象:系统开一上午,操作越来越慢,最后报连接超时。原因:Dao 里 Connection、Statement、ResultSet 没有在 finally 或 try-with-resources 里关闭,连接池被耗尽。解决:所有 JDBC 资源用 try-with-resources 管理,连接从统一的 DbUtil 获取,禁止在界面层直接 new Connection。上线前用连接池监控看一眼活跃连接数是否回落。
4.4 中文乱码与日期格式错乱
现象:员工姓名存进去变问号,日期显示成带时区的长串。原因:数据库连接串没指定字符集,或者用 java.util.Date 直接 toString 展示。解决:连接串加characterEncoding=utf8,建库建表统一 utf8mb4;日期在实体里用 LocalDate,展示时用 DateTimeFormatter 格式化成 yyyy-MM-dd,入库用 setObject 传 LocalDate,别用字符串硬拼。
4.5 薪资重复生成
现象:同一个月点了两次“生成薪资”,薪资表出现两条记录,报表金额翻倍。原因:生成前没查重,表上也没唯一约束。解决:salary 表加 (employee_id, work_month) 唯一索引,Service 里生成前先查该月是否已存在,存在则提示“已生成,是否覆盖”,覆盖走先删后插或 update。数据库约束和业务校验两道防线都要有。
5. 验收与答辩:用一张检查表把系统讲清楚
到了验收环节,评委或导师看的不是你写了多少行代码,而是系统是否闭环、边界是否清楚、异常是否有处理。我一般会准备一张自检表,逐项过一遍再上台。下面这张表可以直接拿去用,左列是检查项,右列是合格标准,答辩前逐条打勾。
| 检查项 | 合格标准 |
|---|---|
| 登录与角色 | 两种角色登录后菜单不同,越权调用被拒 |
| 员工管理 | 增删改查可用,工号重复有提示,离职不物理删除 |
| 考勤导入 | 批量导入有事务,重复月份被拦截并提示 |
| 薪资计算 | 规则集中在 Service,金额用 BigDecimal,可复算 |
| 异常处理 | 空值、格式错误、数据库异常都有友好提示 |
| 数据一致性 | 操作后表格与库一致,刷新沿用查询条件 |
答辩陈述建议按“业务背景 → 模块划分 → 关键技术点 → 演示 → 不足与改进”五段走,控制在八到十分钟。演示时提前准备好数据,别现场从零录入,容易超时。重点讲两三个技术点,比如事务控制怎么保证导入不半途而废、BigDecimal 为什么替代 double、三层结构怎么让改需求只动一处。被问到“为什么用 Swing 不用 Web”时,答桌面端部署简单、离线可用、适合单机人事场景,别贬低其他技术。
有个习惯我从那以后一直保持:每次交付前,把系统在一个干净环境里从建库、导初始数据、走一遍完整业务流程,全程录屏。录屏既是自检,也是答辩时的兜底材料,现场演示出问题可以直接放。希望帮到你。
本文还有配套的精品资源,点击获取