Java Swing人事管理系统源码:Swing+JDBC实现桌面端CRUD全链路
简介:这是一套基于Java Swing开发的桌面人事管理系统源码,面向Java初学者、课程设计学生及需要GUI练手项目的开发者,用于解决企业内部员工信息录入、查询、编辑与数据导入导出等基础管理需求。压缩包共36个文件,包含16个java源文件与16个class编译文件,另有project、prefs、classpath等Eclipse工程配置及一个MySQL驱动jar包,整体约771KB,结构完整可直接导入IDE运行。系统运用JFrame、JTable、JButton、JTextField、JComboBox、JFileChooser等Swing组件构建界面,通过事件监听机制响应用户操作,并可能采用MVC模式分离业务逻辑与视图,数据持久化可借助JDBC连接数据库或文件存储,同时涉及异常处理与错误提示等工程细节。目前已有647人学习下载,适合作为理解Swing组件应用、事件处理与桌面项目架构的参考范例。
1. 从一次“翻车”的课程设计说起:这个 Java Swing 人事管理系统到底能干什么
去年帮一个学弟看他的课程设计,需求文档写得挺唬人——员工档案、考勤、薪资、部门管理全都有,结果跑起来一看,主界面按钮点下去没反应,数据库里查出来的中文全是问号。他跟我说“Swing 是不是已经淘汰了,怎么这么难用”。其实不是 Swing 的问题,是这类桌面端管理系统看着简单,真要把增删改查、表格渲染、事件分发串起来,坑一点都不比 Web 项目少。今天要拆的这个 Java Swing 人事管理系统,就是一份能把上面这些模块完整跑通的源码,适合正在做课程设计、想找一个能直接改的桌面端 CRUD 模板、或者单纯想搞明白 Swing + JDBC 怎么配合的开发者。它不依赖任何前端框架,一个 JDK 加一个 MySQL 就能跑起来,对新手来说反而是最容易看到“数据从界面到数据库再回来”全链路的项目类型。
2. 技术选型与整体架构:为什么是 Swing + JDBC 这套老组合
2.1 分层结构:界面、业务、数据三层怎么切
这个项目没有用 Spring 那一套,走的是最朴素的 MVC 变体。view包放所有 JFrame、JPanel、JDialog,负责画界面和收集输入;service包放业务逻辑,比如“新增员工时先校验工号是否重复”;dao包封装 JDBC 操作,一个实体对应一个 Dao 类;model包就是一堆 POJO,字段和数据库表列一一对应。这种切法的好处是,你改界面不会碰到 SQL,改 SQL 也不会影响按钮布局。常见做法是在dao和service之间再加一层接口,但这个项目为了控制代码量,直接用了实现类,对课程设计来说够用。
目录结构大致长这样:
src/ ├── main/ │ ├── java/ │ │ ├── com/hrms/ │ │ │ ├── view/ # 所有窗体类 │ │ │ ├── service/ # 业务逻辑 │ │ │ ├── dao/ # 数据库访问 │ │ │ ├── model/ # 实体类 │ │ │ └── util/ # 数据库连接、日期工具 │ │ └── resources/ │ │ └── db.properties └── test/2.2 数据库连接:一个容易被忽略的配置点
项目把数据库配置抽到了db.properties里,而不是硬编码在 Java 文件里。这一点很关键,因为很多同学交完作业换台电脑跑,数据库密码不一样就直接连不上。配置文件内容大概是:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/hrms?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456注意characterEncoding=utf8和serverTimezone这两个参数,少了任何一个,要么中文乱码,要么启动就报时区错误。util包里有一个DBUtil类负责读取这个文件并返回Connection,所有 Dao 都通过它拿连接。
2.3 界面组织:主窗体 + 内部面板的切换方式
主界面是一个JFrame,左边放JTree或者一列JButton当导航,右边是一个JPanel容器。点击不同菜单项时,不是弹新窗口,而是把对应的业务面板add到这个容器里再revalidate。这样做的好处是只有一个窗口,用户体验更接近真实的管理系统。核心切换逻辑在MainFrame里:
// 右侧内容面板,所有业务面板都往这里塞 private JPanel contentPanel = new JPanel(new BorderLayout()); // 菜单点击事件里调用 private void switchPanel(JPanel panel) { contentPanel.removeAll(); // 清掉旧面板 contentPanel.add(panel, BorderLayout.CENTER); contentPanel.revalidate(); // 重新布局 contentPanel.repaint(); // 重绘,防止残影 }revalidate和repaint这两句是血泪经验,只add不调用它们,新面板经常显示不出来或者叠在一起。参数上没什么可调的,记住成对出现就行。
3. 核心模块拆解:员工管理从界面到数据库的完整链路
3.1 实体类与表格模型:数据怎么绑到 JTable 上
员工实体Employee有 id、工号、姓名、性别、部门、职位、入职日期、薪资这些字段。Swing 里显示列表用JTable,但JTable不直接吃List<Employee>,需要包一层AbstractTableModel。项目里自定义了一个EmployeeTableModel:
public class EmployeeTableModel extends AbstractTableModel { private final String[] columns = {"ID", "工号", "姓名", "性别", "部门", "职位", "入职日期", "薪资"}; private List<Employee> data = new ArrayList<>(); public void setData(List<Employee> data) { this.data = data; 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.getId(); case 1: return e.getEmpNo(); case 2: return e.getName(); // ... 其余字段 default: return null; } } }这里fireTableDataChanged()是刷新表格的关键,查完数据调一次setData,界面自动更新。如果只改data不调这个方法,表格还是旧数据,很多人第一次写都会卡在这里。
3.2 Dao 层:PreparedStatement 与结果集映射
以查询所有员工为例,Dao 里的写法:
public List<Employee> findAll() { List<Employee> list = new ArrayList<>(); String sql = "SELECT * FROM employee ORDER BY id DESC"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Employee e = new Employee(); e.setId(rs.getInt("id")); e.setEmpNo(rs.getString("emp_no")); e.setName(rs.getString("name")); e.setGender(rs.getString("gender")); e.setDeptId(rs.getInt("dept_id")); e.setHireDate(rs.getDate("hire_date")); e.setSalary(rs.getBigDecimal("salary")); list.add(e); } } catch (SQLException ex) { ex.printStackTrace(); } return list; }用 try-with-resources 自动关连接,避免忘记close导致连接池耗尽。PreparedStatement除了防注入,对日期和金额类型的处理也比拼字符串省心。注意getDate返回的是java.sql.Date,存到实体里如果字段是java.util.Date,需要转一下,项目里在 setter 里做了兼容。
3.3 新增与修改:表单校验和事务边界
新增员工时,界面上的“保存”按钮触发service.addEmployee(emp)。Service 里先查工号是否已存在,再调 Dao 插入。这里有一个容易翻车的点:如果插入员工后还要更新部门人数,这两步必须放在同一个事务里。项目里用了一个简单的TransactionTemplate思路:
public boolean addEmployee(Employee emp) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 if (employeeDao.existsByEmpNo(conn, emp.getEmpNo())) { return false; // 工号重复 } employeeDao.insert(conn, emp); deptDao.incrementCount(conn, emp.getDeptId()); conn.commit(); // 两步都成功才提交 return true; } catch (SQLException e) { if (conn != null) conn.rollback(); e.printStackTrace(); return false; } finally { DBUtil.close(conn); } }参数说明:conn在 Dao 方法里作为参数传入,保证多个操作共用同一个连接,否则事务不生效。setAutoCommit(false)之后必须显式commit或rollback,忘了任何一个都会锁表。
4. 避坑与常见问题:跑不起来时先查这几条
4.1 中文乱码:数据库、连接、界面三处都要对
现象是表格里姓名显示成问号。原因通常有三个:数据库建表时没指定utf8mb4,JDBC URL 里没加characterEncoding=utf8,或者 Swing 组件用了默认字体不支持中文。解决顺序是先确认SHOW CREATE TABLE employee里字符集正确,再检查db.properties的 URL,最后在main方法开头设置UIManager的字体为“微软雅黑”或“宋体”。
4.2 空指针:表格选中行后直接取值
现象是点击“编辑”按钮报NullPointerException。原因是table.getSelectedRow()在没选中任何行时返回 -1,直接拿去data.get(-1)就崩了。解决是在事件处理开头加判断:
int row = table.getSelectedRow(); if (row == -1) { JOptionPane.showMessageDialog(this, "请先选择一行"); return; }4.3 界面卡死:在事件线程里做耗时查询
现象是点击“查询”后窗口无响应。原因是 Swing 的事件分发线程(EDT)里执行了数据库查询,查询期间界面不刷新。解决是把查询放到SwingWorker的doInBackground里,查完在done里更新表格。项目里数据量小可能感觉不到,但一旦联表查询多了就会明显卡顿。
4.4 日期格式:JTable 里显示成英文长格式
现象是入职日期列显示Mon Jan 01 00:00:00 CST 2024。原因是getValueAt直接返回了Date对象,JTable默认用toString。解决是返回格式化后的字符串,或者在EmployeeTableModel里重写getColumnClass返回String.class,再在getValueAt里用SimpleDateFormat转一下。
4.5 连接泄漏:Dao 里忘了关 ResultSet
现象是程序跑一段时间后报“Too many connections”。原因是早期代码里ResultSet没有随Connection一起关闭。解决是统一改成 try-with-resources,把Connection、PreparedStatement、ResultSet都写在 try 的括号里,让 JVM 自动关。
5. 二次开发与改造:从能跑到好用,我一般会动哪几刀
5.1 把硬编码的 SQL 换成配置化查询
项目里查询条件基本是写死的,比如“按姓名模糊查”就是WHERE name LIKE ?。如果你想加一个“按部门 + 入职年份”组合筛选,最省事的改法是在 Dao 里拼一个StringBuilder,根据传入参数动态追加AND条件。但要注意,拼接的是条件片段,值仍然用?占位。我一般会写一个EmployeeQuery对象,把姓名、部门、年份都塞进去,Dao 里统一处理,这样界面层不用关心 SQL 长什么样。
5.2 给表格加一个右键菜单和导出 Excel
JTable默认没有右键菜单,加一个JPopupMenu绑定MouseListener就能实现“导出当前选中行”。导出用Apache POI写XSSFWorkbook,核心代码不超过二十行。这一步做完,课程设计的“创新点”基本就有了。注意 POI 的依赖要加到pom.xml或者lib目录,否则运行时报ClassNotFoundException。
5.3 用 TableRowSorter 做本地筛选
如果不想每次都查数据库,可以直接用JTable自带的排序和筛选:
TableRowSorter<EmployeeTableModel> sorter = new TableRowSorter<>(tableModel); table.setRowSorter(sorter); // 按姓名筛选 sorter.setRowFilter(RowFilter.regexFilter("张", 2)); // 2 是姓名列索引这样输入框里打字就能实时过滤,体验比点按钮查询好很多。参数2是列索引,从 0 开始数,改列的时候记得同步改。
5.4 改造时的一个习惯
我每次改完 Dao 层,都会先写一个main方法单独跑一遍增删改查,确认数据库层面没问题,再去调界面。因为 Swing 的异常经常被事件线程吞掉,界面没反应但控制台也不报错,直接调界面排查效率很低。从那以后我每次动数据库相关代码,都强制先跑一遍 Dao 的单元测试,再启动主窗体。希望这个习惯也能帮到你。
本文还有配套的精品资源,点击获取