Java Swing 人事管理系统实战:从登录到增删改查完整拆解

发布时间:2026/10/23 17:43:06
Java Swing 人事管理系统实战:从登录到增删改查完整拆解 简介这是一套基于Java Swing开发的桌面人事管理系统源码面向Java初学者、课程设计学生及需要GUI练手项目的开发者用于解决企业内部员工信息录入、查询、编辑与数据导入导出等基础管理需求。压缩包共36个文件包含16个java源文件、16个class编译文件以及project、prefs、classpath配置和mysql-connector-java驱动jar整体约771KB源码与工程配置齐全可直接导入Eclipse运行调试。系统运用JFrame、JTable、JButton、JTextField、JComboBox、JFileChooser等Swing组件构建界面通过事件监听实现交互并可能采用MVC架构分离业务逻辑与视图配合JDBC连接数据库完成数据持久化同时涉及异常处理与错误提示机制。目前已有647人学习下载适合作为Swing界面编程、事件处理与数据库操作的实战参考帮助读者理解桌面应用的完整开发流程与代码组织方式。1. 一个能直接跑起来的 Java Swing 人事管理系统到底长什么样很多刚接触 Java 桌面开发的朋友第一次想找一个能跑通、能改的完整项目练手时往往会被网上那些只有半截代码、缺数据库脚本、跑起来一堆空指针的“半成品”劝退。我这次拆的这个 Java Swing 人事管理系统就是冲着“能直接跑、能看懂、能改”这三个目标去的。它把员工档案、部门管理、考勤记录、薪资核算这几块人事日常最核心的业务全部用 Swing 组件搭成了单机桌面应用数据落在本地数据库里不需要额外部署服务端双击就能启动。适合谁一是正在学 Java GUI 编程、想找一个完整 CRUD 项目练手的学生二是需要给小型团队做内部人事台账、又不想上重型系统的开发者。下面我就按“先看清结构、再跑起来、最后动手改”的顺序把这个项目从头到尾拆一遍。2. 项目结构与技术选型为什么用 Swing JDBC 这套组合2.1 目录结构一览拿到源码包后先别急着点运行花两分钟把目录扫一遍后面改代码时找文件会快很多。这个项目采用的是典型的 Maven 工程结构根目录下src/main/java放业务代码src/main/resources放配置和数据库脚本lib目录里是几个必须手动引入的第三方 jar。整体分层很清晰没有把界面和数据库操作揉在一个类里。hr-swing-system/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── hr/ │ │ │ ├── Main.java # 程序入口启动登录窗口 │ │ │ ├── view/ # 所有 Swing 窗体 │ │ │ │ ├── LoginFrame.java │ │ │ │ ├── MainFrame.java │ │ │ │ ├── EmployeePanel.java │ │ │ │ ├── DeptPanel.java │ │ │ │ └── SalaryPanel.java │ │ │ ├── dao/ # 数据库访问层 │ │ │ │ ├── EmployeeDao.java │ │ │ │ ├── DeptDao.java │ │ │ │ └── SalaryDao.java │ │ │ ├── model/ # 实体类 │ │ │ │ ├── Employee.java │ │ │ │ ├── Department.java │ │ │ │ └── Salary.java │ │ │ └── util/ # 工具类 │ │ │ ├── DBUtil.java # 数据库连接 │ │ │ └── DateUtil.java │ │ └── resources/ │ │ ├── db.properties # 数据库连接参数 │ │ └── init.sql # 建表与初始数据 ├── lib/ │ ├── mysql-connector-java-8.0.28.jar │ └── rs2xml.jar # 结果集转表格模型 └── pom.xml这个结构里view只负责画界面和收集用户输入dao只负责拼 SQL 和读写数据库model是纯数据载体。三层各管各的改界面不会碰坏 SQL换数据库也不用重写窗体。常见做法是再加一个service层做业务校验但这个项目体量不大把校验逻辑直接写在dao的方法里也够用新手读起来反而少绕一层。2.2 技术选型背后的取舍Swing 是 JDK 自带的 GUI 库不用装额外运行时打包成 jar 后只要有 JRE 就能跑这对内部小工具来说是最省事的方案。数据库选了 MySQL通过 JDBC 直连没有引入 ORM 框架。为什么不用 Hibernate 或 MyBatis因为人事系统的表结构简单就员工、部门、薪资三张主表手写 SQL 反而更直观也方便新手看清一条数据从界面到数据库的完整链路。rs2xml.jar是个小工具包作用是把ResultSet直接转成DefaultTableModel省去手动遍历结果集往表格里塞数据的重复代码。提示如果你本地没有 MySQL可以把db.properties里的驱动和 URL 换成 H2 或 SQLiteDBUtil里只改一行加载驱动的代码即可后面第 5 章会讲具体怎么换。2.3 数据库表设计三张表的字段不多但覆盖了人事管理最基础的关联关系。员工表通过dept_id关联部门表薪资表通过emp_id关联员工表删除部门时会先检查下面还有没有员工避免出现悬空外键。-- 部门表 CREATE TABLE department ( dept_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL, dept_head VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 员工表 CREATE TABLE employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT, emp_name VARCHAR(20) NOT NULL, gender CHAR(2), phone VARCHAR(15), dept_id INT, hire_date DATE, status TINYINT DEFAULT 1, -- 1在职 0离职 FOREIGN KEY (dept_id) REFERENCES department(dept_id) ); -- 薪资表 CREATE TABLE salary ( salary_id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT, base_salary DECIMAL(10,2), bonus DECIMAL(10,2), pay_month VARCHAR(7), -- 格式 2024-06 FOREIGN KEY (emp_id) REFERENCES employee(emp_id) );status字段用 1 和 0 标记在职与离职查询时默认只查在职员工离职员工保留在库里方便回溯历史薪资。pay_month用字符串存月份而不是日期是因为薪资按月发放不需要精确到日这样查询某月薪资时直接字符串匹配就行不用做日期函数转换。3. 核心模块拆解从登录到增删改查的完整链路3.1 数据库连接工具类整个项目所有数据库操作都走DBUtil这一个入口连接参数放在db.properties里改数据库地址不用翻代码。这里用的是每次操作新建连接、用完即关的方式没有上连接池。对于单机桌面应用同时只有一个用户在操作连接池带来的性能提升几乎为零反而增加配置复杂度。public class DBUtil { private static String url; private static String user; private static String password; // 静态块在类加载时读取配置文件只执行一次 static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(in); url props.getProperty(jdbc.url); user props.getProperty(jdbc.user); password props.getProperty(jdbc.password); Class.forName(props.getProperty(jdbc.driver)); } catch (Exception e) { e.printStackTrace(); } } // 获取连接 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } // 统一关闭资源顺序不能反先关结果集再关语句最后关连接 public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }db.properties里四个键分别对应驱动类名、连接地址、用户名和密码。连接地址后面要带上useSSLfalseserverTimezoneAsia/Shanghai这两个参数否则 MySQL 8 会报时区错误和 SSL 警告这是新手最容易翻车的地方之一。close方法的关闭顺序必须和打开顺序相反先关ResultSet再关Statement最后关Connection顺序写反在某些驱动下会抛异常。3.2 员工管理面板表格 表单的经典布局员工管理是使用频率最高的模块界面左边是员工列表表格右边是编辑表单顶部一排操作按钮。这种布局在 Swing 里用BorderLayout套JSplitPane就能实现左边表格放JScrollPane里自动出滚动条。public class EmployeePanel extends JPanel { private JTable table; private DefaultTableModel tableModel; private JTextField nameField, phoneField; private JComboBoxString deptBox, genderBox; private EmployeeDao employeeDao new EmployeeDao(); public EmployeePanel() { setLayout(new BorderLayout()); // 顶部按钮栏 JPanel topPanel new JPanel(new FlowLayout(FlowLayout.LEFT)); JButton addBtn new JButton(新增); JButton updateBtn new JButton(修改); JButton deleteBtn new JButton(删除); JButton refreshBtn new JButton(刷新); topPanel.add(addBtn); topPanel.add(updateBtn); topPanel.add(deleteBtn); topPanel.add(refreshBtn); // 中间表格列名与数据库字段对应 String[] columns {工号, 姓名, 性别, 电话, 部门, 入职日期, 状态}; tableModel new DefaultTableModel(columns, 0) { Override public boolean isCellEditable(int row, int col) { return false; // 表格只读编辑走右侧表单 } }; table new JTable(tableModel); table.setRowHeight(28); JScrollPane scrollPane new JScrollPane(table); add(topPanel, BorderLayout.NORTH); add(scrollPane, BorderLayout.CENTER); // 绑定事件 refreshBtn.addActionListener(e - loadEmployees()); deleteBtn.addActionListener(e - deleteSelected()); table.getSelectionModel().addListSelectionListener(e - fillFormFromTable()); loadEmployees(); // 构造时加载一次数据 } // 从数据库拉取全部在职员工填充表格 private void loadEmployees() { tableModel.setRowCount(0); // 先清空旧数据 ListEmployee list employeeDao.findAll(); for (Employee emp : list) { tableModel.addRow(new Object[]{ emp.getEmpId(), emp.getEmpName(), emp.getGender(), emp.getPhone(), emp.getDeptName(), emp.getHireDate(), emp.getStatus() 1 ? 在职 : 离职 }); } } }isCellEditable返回false是关键否则用户双击表格就能改数据但改完不会同步到数据库造成界面和库里的数据不一致。所有修改必须走右侧表单和“修改”按钮保证每次变更都经过dao层。loadEmployees里先setRowCount(0)再重新添加行这是刷新表格的标准做法比逐行删除快也不会残留旧数据。3.3 数据访问层手写 SQL 的增删改查EmployeeDao里每个方法都遵循“获取连接 → 准备语句 → 执行 → 处理结果 → 关闭资源”的固定套路。查询返回ListEmployee增删改返回受影响行数。public class EmployeeDao { // 查询所有在职员工关联部门表取部门名称 public ListEmployee findAll() { ListEmployee list new ArrayList(); String sql SELECT e.*, d.dept_name FROM employee e LEFT JOIN department d ON e.dept_id d.dept_id WHERE e.status 1 ORDER BY e.emp_id; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Employee emp new Employee(); emp.setEmpId(rs.getInt(emp_id)); emp.setEmpName(rs.getString(emp_name)); emp.setGender(rs.getString(gender)); emp.setPhone(rs.getString(phone)); emp.setDeptName(rs.getString(dept_name)); emp.setHireDate(rs.getDate(hire_date)); emp.setStatus(rs.getInt(status)); list.add(emp); } } catch (SQLException e) { e.printStackTrace(); } return list; } // 新增员工返回自增主键 public int insert(Employee emp) { String sql INSERT INTO employee(emp_name, gender, phone, dept_id, hire_date, status) VALUES(?, ?, ?, ?, ?, 1); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, emp.getEmpName()); ps.setString(2, emp.getGender()); ps.setString(3, emp.getPhone()); ps.setInt(4, emp.getDeptId()); ps.setDate(5, new java.sql.Date(emp.getHireDate().getTime())); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } } }这里用了 try-with-resources 语法连接、语句、结果集会在代码块结束时自动关闭不用再手动调DBUtil.close。PreparedStatement的占位符从 1 开始编号和数组下标从 0 开始不一样写的时候容易数错。LEFT JOIN保证即使员工没有分配部门员工记录也能查出来部门名称显示为空而不是整条记录消失。3.4 登录与权限控制登录窗口校验用户名密码后把当前登录用户存到一个静态变量里主窗口根据角色决定哪些按钮可见。这个项目只做了简单的管理员和普通用户两级没有引入复杂的权限框架。public class LoginFrame extends JFrame { private JTextField userField; private JPasswordField passField; private void doLogin() { String username userField.getText().trim(); String password new String(passField.getPassword()); if (username.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(this, 用户名和密码不能为空); return; } UserDao userDao new UserDao(); User user userDao.login(username, password); if (user ! null) { CurrentUser.set(user); // 存入全局静态变量 dispose(); // 关闭登录窗 new MainFrame().setVisible(true); } else { JOptionPane.showMessageDialog(this, 用户名或密码错误); } } }密码在数据库里存的是 MD5 摘要登录时把用户输入的密码同样做 MD5 再比对不存明文。CurrentUser是一个只有静态字段的类主窗口构造时读它的role字段如果是普通用户就把“删除”和“薪资核算”按钮设为不可见。这种权限控制很粗但胜在简单改起来也快后面第 5 章会讲怎么把它换成更细的按钮级控制。4. 避坑与常见问题排查4.1 启动报“找不到或无法加载主类”现象是双击 jar 或者命令行java -jar时直接报这个错。原因通常是打包时没有指定主类或者pom.xml里maven-jar-plugin的mainClass配置写错了。解决方法是检查pom.xml中manifest配置确保mainClass指向com.hr.Main然后重新执行mvn clean package。如果用的是 IDE 直接运行检查运行配置里的主类是否选对。4.2 中文乱码现象是界面上的中文显示成方块或者数据库里的中文变成问号。原因有两个层面一是 JVM 启动参数没指定字符集二是数据库连接 URL 没带字符集参数。解决方法是在启动命令里加-Dfile.encodingUTF-8同时在db.properties的 URL 后面追加characterEncodingutf8。如果数据库已经存了乱码数据需要先把表字符集改成utf8mb4再重新导入init.sql。4.3 表格数据不刷新现象是新增或修改员工后表格里还是旧数据。原因是loadEmployees没有被重新调用或者调用前没有清空tableModel。解决方法是在每个增删改操作成功后显式调用一次loadEmployees()并且确保方法开头有tableModel.setRowCount(0)。如果表格绑定了RowSorter刷新后排序会重置这是正常现象需要重新点列头排序。4.4 删除部门时抛外键约束异常现象是删除一个还有员工的部门时控制台报Cannot delete or update a parent row。原因是员工表的dept_id外键指向了该部门数据库不允许直接删。解决方法是在DeptDao.delete里先查该部门下的员工数量如果大于零就弹窗提示“该部门下还有员工无法删除”而不是直接把异常抛给用户看。4.5 打包后图片和配置文件丢失现象是 IDE 里跑得好好的打成 jar 后图标不显示、数据库连不上。原因是src/main/resources下的文件默认会被打进 jar但如果你把图片放在项目根目录而不是 resources 下打包时就不会被包含。解决方法是把所有资源文件统一放到src/main/resources对应子目录下代码里用getClass().getResourceAsStream(/xxx)读取不要用new File(xxx)这种相对路径。5. 二次开发与改造思路5.1 换数据库从 MySQL 迁到 SQLite有些场景下不想装 MySQL比如给非技术同事用的单机工具直接换成 SQLite 最省事。改动集中在三处第一把lib里的 MySQL 驱动换成sqlite-jdbc.jar第二改db.properties的驱动类名为org.sqlite.JDBCURL 改成jdbc:sqlite:hr.db第三init.sql里的AUTO_INCREMENT改成AUTOINCREMENTDATETIME DEFAULT CURRENT_TIMESTAMP语法 SQLite 也支持不用动。改完跑一遍如果建表报错多半是字段类型不兼容把TINYINT换成INTEGER即可。5.2 加一个导出 Excel 的功能人事同事最常提的需求就是“能不能把员工列表导出来”。在EmployeePanel顶部加一个“导出”按钮点击后把tableModel里的数据写进 Excel。用 Apache POI 库核心代码不到二十行private void exportExcel() { try (XSSFWorkbook workbook new XSSFWorkbook()) { XSSFSheet sheet workbook.createSheet(员工列表); // 写表头 Row header sheet.createRow(0); for (int i 0; i tableModel.getColumnCount(); i) { header.createCell(i).setCellValue(tableModel.getColumnName(i)); } // 写数据行 for (int i 0; i tableModel.getRowCount(); i) { Row row sheet.createRow(i 1); for (int j 0; j tableModel.getColumnCount(); j) { Object val tableModel.getValueAt(i, j); row.createCell(j).setCellValue(val null ? : val.toString()); } } // 弹出保存对话框 JFileChooser chooser new JFileChooser(); chooser.setSelectedFile(new File(员工列表.xlsx)); if (chooser.showSaveDialog(this) JFileChooser.APPROVE_OPTION) { try (FileOutputStream out new FileOutputStream(chooser.getSelectedFile())) { workbook.write(out); } JOptionPane.showMessageDialog(this, 导出成功); } } catch (IOException e) { JOptionPane.showMessageDialog(this, 导出失败 e.getMessage()); } }XSSFWorkbook对应.xlsx格式如果要用.xls就换成HSSFWorkbook。JFileChooser的setSelectedFile只是给个默认文件名用户仍然可以改路径。导出前最好检查一下tableModel.getRowCount()是否为零零行导出会得到一个只有表头的空文件体验不好。5.3 把硬编码的权限改成配置化现在的权限判断散落在各个按钮的setVisible调用里加一个角色就要改好几处。我一般会抽一个PermissionConfig类用MapString, ListString存角色和可访问模块的对应关系主窗口初始化时遍历所有按钮根据当前用户角色统一设置可见性。这样以后加角色只需要改配置不用动界面代码。改完之后记得写一个简单的验证用管理员登录看所有按钮是否都在用普通用户登录看受限按钮是否隐藏两个角色各跑一遍增删改查确认没有漏网的入口。5.4 一个我踩过的坑早些年我改这类 Swing 项目时习惯直接在dao里写System.out.println打日志觉得方便。结果有一次打包给同事用同事反馈“点一下卡三秒”我查了半天才发现是某条 SQL 在循环里被反复执行控制台疯狂输出把 IO 拖慢了。从那以后我每次改dao层都强制走一遍“先看 SQL 有没有在循环里、再看有没有不必要的全表查询、最后确认日志级别”这三步。桌面应用虽然并发低但界面线程被阻塞一样会卡死这个习惯帮我省了不少后悔药。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询