简介这份资源是一套基于 Java Swing 开发的桌面人事管理系统源码面向正在学习 Java GUI 编程、需要完成课程设计或想积累桌面应用实战经验的学生与开发者可用于理解如何把界面组件、事件处理与数据持久化串成完整业务闭环。压缩包共 36 个文件以 16 个 java 源文件和 16 个 class 编译文件为主体另含工程配置与依赖 jar整体约 771KB体积轻量便于本地导入 IDE 直接运行调试。系统覆盖员工信息录入、编辑、删除与按姓名、部门、职位等条件查询并支持数据导入导出界面层用到 JFrame、JTable、JButton、JTextField、JComboBox 与 JFileChooser 等组件配合事件监听完成交互。读者可从中借鉴 MVC 分层组织、JDBC 连接数据库、异常提示与文件存储等实现思路对照源码梳理各模块职责快速搭建自己的管理类项目骨架。目前已有 647 人学习下载。1. 一个桌面端人事系统为什么还有人用 Swing 写前阵子帮一个做行政系统的朋友看代码他丢过来一个压缩包说“你帮我看看这玩意儿还能不能跑”。解压之后发现是个 Java Swing 写的人事管理系统界面不算花哨但员工档案、部门管理、考勤记录、薪资核算这些模块一个不少数据落在本地数据库里双击就能启动不需要装浏览器、不需要配服务器。这类项目在高校课程设计和中小企业内部工具里其实一直有市场原因很直接部署成本低、单机可跑、代码量可控新手拿来做二次开发的起点非常合适。Java Swing 人事管理系统这个关键词背后读者真正想知道的不是 Swing 还能不能打而是这套东西拿到手之后怎么跑起来、数据存哪、界面和业务怎么分层、想加个字段或者改个报表该动哪几个文件。这篇就按我拆包的顺序从目录结构一路讲到改造点能抄的代码我尽量贴全。2. 项目骨架与运行环境从目录结构到第一次启动2.1 目录长什么样每个包负责什么拿到源码先别急着点运行先把目录树扫一遍。这类 Swing 人事管理系统常见的结构是按职责分包而不是按功能分包典型布局如下hr-system/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/hr/ │ │ │ ├── App.java // 启动入口 │ │ │ ├── ui/ // 所有 Swing 窗体 │ │ │ ├── service/ // 业务逻辑 │ │ │ ├── dao/ // 数据库访问 │ │ │ ├── model/ // 实体类 │ │ │ └── util/ // 工具类、连接池、常量 │ │ └── resources/ │ │ ├── db.properties // 数据库连接配置 │ │ └── sql/init.sql // 建表与初始数据 ├── lib/ // 第三方 jar └── pom.xml 或 build.gradleui包里通常一个窗体一个类比如LoginFrame、MainFrame、EmployeePanelservice层负责校验和事务编排dao层只做增删改查model里是Employee、Department、Attendance这类 POJO。这种分法不算精致但对桌面端够用改起来也好定位——界面问题进ui算错了进service查不出数据进dao。2.2 环境准备与启动命令运行环境这块JDK 8 到 JDK 17 都能跑Swing 本身没有版本门槛真正卡人的是数据库驱动和构建工具。如果项目带pom.xml直接# 确认 JDK 版本Swing 项目 8 以上均可 java -version # Maven 构建并拉取依赖 mvn clean package -DskipTests # 运行打出来的 jar注意带上依赖 java -jar target/hr-system-1.0-jar-with-dependencies.jar如果项目是传统lib/放 jar 的结构没有构建工具那就用javac手动编译把lib下所有 jar 加进 classpath# Linux / macOS 下批量拼 classpath CP$(ls lib/*.jar | tr \n :) javac -encoding UTF-8 -cp $CP -d out $(find src -name *.java) java -cp out:$CP com.example.hr.AppWindows 下把:换成;即可。这里有个血泪经验源码里如果有中文注释或中文字符串javac不加-encoding UTF-8很容易编译报错或者界面乱码第一次跑不通八成栽在这。2.3 数据库配置改一个 properties 就能连上桌面端人事系统一般用 MySQL 或 SQLite。MySQL 需要先建库再导init.sqlSQLite 则是文件即数据库拷过去就能用。连接参数集中在db.properties# db.properties 典型内容 jdbc.urljdbc:mysql://localhost:3306/hr_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456 jdbc.drivercom.mysql.cj.jdbc.Driver读取这段配置的工具类通常长这样逻辑很简单但值得看一眼因为改数据库类型时就要动它public class DBUtil { private static final Properties props new Properties(); static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { props.load(in); // 驱动只加载一次JDBC 4 之后其实可省略但老项目保留着 Class.forName(props.getProperty(jdbc.driver)); } catch (Exception e) { throw new ExceptionInInitializerError(数据库配置加载失败: e.getMessage()); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection( props.getProperty(jdbc.url), props.getProperty(jdbc.username), props.getProperty(jdbc.password)); } }static块在类加载时执行一次负责读配置和注册驱动getConnection每次新建连接。这种写法没有连接池单机小系统够用但如果后面要做批量导入或者多窗口并发查询建议换成 HikariCP改造成本也就是把getConnection内部换成从DataSource取连接调用方一行不用动。2.4 启动入口与主窗体装配App.java一般只做三件事设置外观、显示登录窗、登录成功后切主窗。Swing 的线程模型要求界面更新放在事件分发线程EDT里规范写法是SwingUtilities.invokeLaterpublic class App { public static void main(String[] args) { // 统一外观避免不同系统下控件风格差异过大 try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception ignored) { } SwingUtilities.invokeLater(() - { LoginFrame login new LoginFrame(); login.setVisible(true); }); } }invokeLater把创建窗体的动作排进 EDT 队列避免主线程和 EDT 抢资源导致界面卡死或偶发空白。登录窗里校验账号密码走service层成功后dispose()掉登录窗再new MainFrame().setVisible(true)。这一步如果写成登录窗不销毁直接开主窗任务栏会残留一个隐藏窗口属于新手常见翻车点。3. 核心模块拆解登录、员工档案与考勤的完整链路3.1 登录鉴权从密码框到角色分流登录模块虽小但它是理解整个项目分层的最好入口。界面上两个输入框加一个按钮按钮的ActionListener里不该直接写 SQL而是调service// LoginFrame 中的登录按钮逻辑 loginBtn.addActionListener(e - { String username userField.getText().trim(); // 密码框取的是 char[]转字符串后交给 service String password new String(passField.getPassword()); if (username.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(this, 账号和密码不能为空); return; } try { User user userService.login(username, password); if (user null) { JOptionPane.showMessageDialog(this, 账号或密码错误); return; } // 按角色决定主窗里哪些菜单可见 MainFrame main new MainFrame(user); main.setVisible(true); this.dispose(); } catch (SQLException ex) { JOptionPane.showMessageDialog(this, 数据库连接异常 ex.getMessage()); } });userService.login内部做两件事查用户、比对密码。密码存储如果明文二次开发第一件事就该改成加盐哈希常见做法是MessageDigest加随机盐存两列登录时用同样盐算一遍比对。MainFrame构造函数接收User对象根据user.getRole()决定是否显示“薪资管理”“系统设置”这些敏感菜单这是桌面端最朴素的权限控制够用但别指望它防得住反编译真要安全还得服务端校验。3.2 员工档案JTable 加表格模型的增删改查员工管理是使用频率最高的模块界面主体是一张JTable数据来自EmployeeDao.findAll()。Swing 的表格不直接吃List要包一层TableModel推荐继承AbstractTableModel而不是用默认的DefaultTableModel因为前者能控制单元格是否可编辑、列类型是什么public class EmployeeTableModel extends AbstractTableModel { private final String[] columns {工号, 姓名, 部门, 职位, 入职日期, 状态}; private ListEmployee data new ArrayList(); public void setData(ListEmployee list) { this.data list; // 通知表格整体刷新行数变化时必须调 fireTableDataChanged(); } Override public int getRowCount() { return data.size(); } Override public int getColumnCount() { return columns.length; } Override public String getColumnName(int col) { return columns[col]; } Override public Object getValueAt(int row, int col) { Employee e data.get(row); switch (col) { case 0: return e.getEmpNo(); case 1: return e.getName(); case 2: return e.getDeptName(); case 3: return e.getPosition(); case 4: return e.getHireDate(); case 5: return e.getStatus() 1 ? 在职 : 离职; default: return ; } } // 只允许“状态”列直接编辑其余走弹窗表单 Override public boolean isCellEditable(int row, int col) { return col 5; } }getValueAt里把实体字段映射到列isCellEditable控制哪些列能就地改。新增和编辑一般弹一个JDialog表单保存时收集字段组装Employee调service.save(employee)成功后重新findAll()再setData。这里有个性能细节如果员工上千条每次保存都全量重查会明显卡顿常见做法是保存成功后只更新对应行调fireTableRowsUpdated(row, row)比全量刷新顺滑得多。3.3 考勤与薪资业务层的事务边界考勤模块通常是“选日期、选员工、标记出勤状态”薪资模块则是“按月汇总考勤加基本工资算实发”。这两个模块的价值在于它们跨了多张表是观察service层事务处理的好样本public class SalaryService { // 按月生成薪资单先删旧记录再批量插入保证幂等 public void generateMonthlySalary(String month) throws SQLException { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 手动事务开始 ListEmployee employees employeeDao.findActive(conn); salaryDao.deleteByMonth(conn, month); // 重算前清掉旧数据 for (Employee e : employees) { int attendDays attendanceDao.countPresent(conn, e.getId(), month); BigDecimal base e.getBaseSalary(); // 简单规则全勤 22 天缺勤按天扣 BigDecimal actual base.multiply(BigDecimal.valueOf(attendDays)) .divide(BigDecimal.valueOf(22), 2, RoundingMode.HALF_UP); salaryDao.insert(conn, new Salary(e.getId(), month, base, actual)); } conn.commit(); } catch (SQLException ex) { if (conn ! null) conn.rollback(); // 任何一步失败整体回滚 throw ex; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } } }关键点在setAutoCommit(false)到commit()之间删旧薪资和插新薪资必须同生共死否则中途异常会留下“删了没插”的空月份。BigDecimal的divide必须指定精度和舍入模式不然除不尽直接抛ArithmeticException这是金额计算的老坑。DAO 方法都接收外部传入的Connection而不是各自DBUtil.getConnection()就是为了让它们共享同一个事务这个设计约定在二次开发时要守住。4. 避坑与排查跑不起来时先看这几处4.1 界面中文变方块或乱码现象是按钮文字、表格表头显示成方块或问号。原因通常是编译时没指定编码或者运行环境默认字体不含中文字形。解决分两步编译加-encoding UTF-8如果还不行在main里显式设置字体比如UIManager.put(Label.font, new Font(微软雅黑, Font.PLAIN, 13))Linux 下换成系统已装的中文字体名。别去改每个JLabel的字体统一在UIManager里设默认值最省事。4.2 报 No suitable driver 或时区错误现象是启动后点登录弹“数据库连接异常”堆栈里是No suitable driver found或The server time zone value is unrecognized。前者是驱动 jar 没进 classpath检查lib下有没有mysql-connector-java手动编译时-cp有没有带上后者是 MySQL 8 的时区问题在 URL 后面补serverTimezoneAsia/Shanghai即可。这两个错误占了新手跑不通的一大半先查这两处再怀疑代码。4.3 表格数据改了但界面不刷新现象是新增员工后数据库里有记录表格还是旧数据。原因是直接给JTable换了List引用却没通知模型或者调了setData但模型里没触发fireTableDataChanged()。Swing 的表格是“模型驱动”的任何数据变更都必须通过fireXxx系列方法通知视图直接操作底层集合是无效的。排查时在setData里打个日志确认被调用再看fire方法有没有漏。4.4 点击按钮界面假死现象是执行导入、批量算薪这类耗时操作时窗口卡住、按钮点不动。原因是耗时逻辑跑在 EDT 上阻塞了事件分发。解决是把业务放进SwingWorker的doInBackground完成后在done()里更新界面new SwingWorkerVoid, Void() { Override protected Void doInBackground() throws Exception { salaryService.generateMonthlySalary(month); // 后台线程执行 return null; } Override protected void done() { try { get(); // 捕获后台异常 JOptionPane.showMessageDialog(null, 生成完成); reloadTable(); } catch (Exception ex) { JOptionPane.showMessageDialog(null, 失败 ex.getCause().getMessage()); } } }.execute();doInBackground里绝对不能碰任何 Swing 组件done()才回到 EDT这条边界踩一次就记住了。4.5 打包成 jar 后图片和配置丢失现象是 IDE 里跑得好好的java -jar启动后图标空白、读不到db.properties。原因是用了new File(src/main/resources/...)这种文件路径打包后资源在 jar 内部文件系统路径不存在。统一改成getClass().getClassLoader().getResourceAsStream(db.properties)流式读取图片用getResource(/icons/logo.png)拿 URL 再new ImageIcon(url)。凡是资源一律走 classpath不走相对路径。5. 二次开发改造加字段、换数据库与打包分发5.1 给员工表加一个字段要动哪几处这是最高频的改造需求。以加“手机号”为例按依赖顺序改五处init.sql加列并执行ALTER TABLE employee ADD COLUMN phone VARCHAR(20)Employee实体加phone字段和 getter/setterEmployeeDao的 insert、update、select 语句补上该列EmployeeTableModel的columns和getValueAt加一列编辑弹窗EmployeeDialog加一个JTextField并在保存时setPhone。顺序不能乱先改实体再改 DAO 最后改界面否则编译一路飘红。改完用一条真实数据走一遍“新增—列表显示—编辑—再保存”确认读写闭环。5.2 从 MySQL 换到 SQLite 的适配点有些场景要求绿色免安装把 MySQL 换成 SQLite 是常见诉求。改动集中在三处db.properties换 URL 和驱动jdbc:sqlite:hr.db、org.sqlite.JDBCinit.sql里的AUTO_INCREMENT改成AUTOINCREMENTDATETIME类型基本兼容DAO 里如果有 MySQL 特有函数比如DATE_FORMAT要换成 SQLite 的strftime。事务代码不用动JDBC 接口是统一的。换完重点测分页查询和日期比较这两处最容易因方言差异翻车。5.3 打包分发的两种方式内部使用推荐maven-shade-plugin打成包含依赖的 fat jar双击即跑如果要给非技术同事用再用jpackageJDK 14打成带运行时的安装包对方机器不用装 JDK# 生成 Windows 安装包--input 指向 fat jar 所在目录 jpackage --type msi --name HRSystem \ --input target/ --main-jar hr-system-1.0-jar-with-dependencies.jar \ --main-class com.example.hr.App --win-shortcut--main-class必须写全限定名--input目录里只放目标 jar别把源码目录塞进去否则包体积翻几倍。5.4 一个验证改造是否成功的习惯我改这类桌面项目有个固定动作每次动完 DAO 或实体先不点界面写一个临时main方法直接调service跑一遍增删改查打印结果确认数据层没问题再去调界面。这样能把“数据错”和“界面没刷新”两类问题分开省掉大量来回猜的时间。从那以后我每次给 Swing 项目加字段或换库都强制先跑一遍数据层自测再开界面翻车次数少了一大半。希望这套拆解能帮到你拿到源码先按第 2 章把环境跑通再照着第 5 章动第一刀。本文还有配套的精品资源点击获取