简介这是一套面向Java初学者与课程设计学习者的员工工资管理系统完整源码采用Swing做桌面界面、MySQL做数据存储适合用来练习桌面应用开发、JDBC数据库操作与增删改查业务逻辑。系统分为管理员与普通用户两种角色管理员可管理员工信息、部门信息与工资数据普通用户则以匿名方式登录并查询工资功能划分清晰便于理解权限与模块设计。压缩包共133个文件约2.1MB其中109个class为编译产物、16个java为可阅读源码另含2个sql建表脚本、jar依赖及工程配置文件sql脚本放在sql文件夹下导入后即可运行。该资源已有3960人学习下载经多次测试运行无误读者可借此掌握Swing界面布局、事件处理、数据库连接与工资统计等核心技能并在此基础上二次开发或作为毕业设计参考。1. JavaSwingMysql员工工资管理系统从建表到打包一个能跑通的桌面端方案很多做 Java 课程设计或公司内部小工具的人都会碰到同一个场景需要一套能录入员工、算工资、查历史记录的桌面程序但不想上 Web 框架也不想配一堆中间件。JavaSwingMysql 实现员工工资管理系统正好卡在这个需求上——Swing 负责界面Mysql 负责持久化JDBC 负责把两边接起来。它适合两类人一是正在找 java课程设计案例源码 的学生二是需要给行政或财务做单机版工资录入工具的一线开发者。这套方案的核心难点不在界面画得多漂亮而在工资计算规则怎么落表、事务怎么保证一致性、以及打包后连不上数据库怎么排查。下面按建库、连库、算薪、避坑、进阶五步走每一步都给可复制的代码和参数。2. 先定表结构再写界面工资管理系统的数据库设计2.1 三张核心表怎么拆员工、部门、工资记录工资管理系统的数据模型不复杂但拆得不好后面改字段会非常痛苦。我一般拆成三张表employee存员工基础信息department存部门salary_record存每月工资明细。不要把工资字段直接塞进employee否则查历史工资时只能靠备份表这是血泪经验。-- 部门表 CREATE TABLE department ( dept_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL UNIQUE, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 员工表 CREATE TABLE employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT, emp_name VARCHAR(30) NOT NULL, gender CHAR(1) DEFAULT 男, dept_id INT, base_salary DECIMAL(10,2) NOT NULL DEFAULT 0.00, hire_date DATE, status TINYINT DEFAULT 1 COMMENT 1在职 0离职, FOREIGN KEY (dept_id) REFERENCES department(dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 工资记录表 CREATE TABLE salary_record ( record_id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, salary_month CHAR(7) NOT NULL COMMENT 格式 2024-06, base DECIMAL(10,2) DEFAULT 0.00, bonus DECIMAL(10,2) DEFAULT 0.00, deduction DECIMAL(10,2) DEFAULT 0.00, social_security DECIMAL(10,2) DEFAULT 0.00, real_salary DECIMAL(10,2) DEFAULT 0.00, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_emp_month (emp_id, salary_month), FOREIGN KEY (emp_id) REFERENCES employee(emp_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明salary_record上的uk_emp_month唯一索引是关键它防止同一个人同一个月被重复录入这是保证数据一致性的第一道闸。real_salary不靠程序算完再存而是每次插入前由 Java 层计算后写入查询时直接读避免每次列表都重算。参数说明金额字段统一用DECIMAL(10,2)不要用FLOAT否则算个税时会出现0.30000000000000004这种玄学尾数。salary_month用CHAR(7)存yyyy-MM比存DATE更好做按月分组查询。2.2 用 Navicat 还是命令行建库初始化脚本与字符集设置如果你本地装了 Navicat for MySQL直接右键新建数据库字符集选utf8mb4排序规则选utf8mb4_general_ci。但更推荐把上面的 SQL 存成init.sql用命令行执行方便换机器时一键重建。# 登录并执行初始化脚本 mysql -u root -p init.sql # 验证表是否建好 mysql -u root -p -e USE salary_db; SHOW TABLES;逻辑说明命令行方式的好处是脚本可版本管理团队里谁把表改坏了重新跑一遍init.sql就能恢复。注意mysql -u root -p init.sql里的是输入重定向不是比较符号。参数说明执行前确认init.sql开头有CREATE DATABASE IF NOT EXISTS salary_db DEFAULT CHARSET utf8mb4;和USE salary_db;否则会报No database selected。如果遇到ERROR 2002 (HY000): Cant connect to local MySQL server through socket说明 MySQL 服务没启动Linux 下用systemctl start mysqldWindows 下在服务面板启动 MySQL80。3. Swing 界面与 JDBC 打通从登录窗到工资列表3.1 一个可复用的 JDBC 工具类连接池要不要上课程设计级别通常直接用DriverManager就够了但如果你想让程序在连续录入 200 条工资时不卡建议引入 Druid 或 HikariCP。下面先给最简版再给连接池版。// DBUtil.java 最简版 import java.sql.*; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/salary_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PWD your_password; public static Connection getConn() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } public static void close(Connection c, Statement s, ResultSet r) { try { if (r ! null) r.close(); } catch (Exception ignored) {} try { if (s ! null) s.close(); } catch (Exception ignored) {} try { if (c ! null) c.close(); } catch (Exception ignored) {} } }逻辑说明getConn()每次调用都新建连接适合低频操作。close()按 ResultSet → Statement → Connection 顺序关闭反过来关会抛异常。参数说明useSSLfalse避免本地开发时的 SSL 连接错误serverTimezoneAsia/Shanghai不加会报时区异常characterEncodingutf8防止中文姓名乱码。生产环境把密码写死在代码里是踩坑行为至少用Properties文件加载。连接池版只需替换getConn()// 使用 HikariCP import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; public class DBUtil { private static HikariDataSource ds; static { HikariConfig cfg new HikariConfig(); cfg.setJdbcUrl(jdbc:mysql://localhost:3306/salary_db?useSSLfalseserverTimezoneAsia/Shanghai); cfg.setUsername(root); cfg.setPassword(your_password); cfg.setMaximumPoolSize(10); cfg.setMinimumIdle(2); ds new HikariDataSource(cfg); } public static Connection getConn() throws SQLException { return ds.getConnection(); } }参数说明maximumPoolSize10对单机 Swing 程序足够设太大反而占内存。minimumIdle2保证空闲时也有连接可用减少首次点击的等待感。3.2 登录窗与主窗的事件绑定别在 EDT 里查数据库Swing 的所有界面更新必须在事件调度线程EDT里做但数据库查询是耗时操作放 EDT 里会让界面假死。正确做法是用SwingWorker。// 登录按钮的 ActionListener loginBtn.addActionListener(e - { String user userField.getText().trim(); String pass new String(passField.getPassword()); loginBtn.setEnabled(false); new SwingWorkerBoolean, Void() { Override protected Boolean doInBackground() throws Exception { // 后台线程查数据库 String sql SELECT COUNT(*) FROM admin WHERE username? AND password?; try (Connection c DBUtil.getConn(); PreparedStatement ps c.prepareStatement(sql)) { ps.setString(1, user); ps.setString(2, pass); try (ResultSet rs ps.executeQuery()) { rs.next(); return rs.getInt(1) 0; } } } Override protected void done() { loginBtn.setEnabled(true); try { if (get()) { new MainFrame().setVisible(true); dispose(); } else { JOptionPane.showMessageDialog(null, 用户名或密码错误); } } catch (Exception ex) { JOptionPane.showMessageDialog(null, 连接失败 ex.getMessage()); } } }.execute(); });逻辑说明doInBackground()在后台线程执行 SQLdone()回到 EDT 更新界面。这样即使数据库响应慢按钮也只是暂时禁用窗口不会白屏。参数说明PreparedStatement用?占位不要用字符串拼接否则输入 OR 11就能绕过登录。getPassword()返回char[]用完手动置零更安全课程设计里至少别把它转成 String 存着。4. 工资计算与事务个税、社保、实发怎么落库4.1 工资公式拆解应发、社保、个税、实发工资计算规则每个公司不同但通用结构是应发 基本工资 奖金 - 考勤扣款社保 应发 × 社保比例个税 (应发 - 社保 - 起征点) × 税率 - 速算扣除数实发 应发 - 社保 - 个税。下面给一个可配置的 Java 实现。public class SalaryCalculator { // 社保个人比例可按城市调整 private static final double SOCIAL_RATE 0.105; // 个税起征点 private static final double TAX_THRESHOLD 5000.0; // 税率表{级距上限, 税率, 速算扣除数} private static final double[][] TAX_TABLE { {3000, 0.03, 0}, {12000, 0.10, 210}, {25000, 0.20, 1410}, {35000, 0.25, 2660}, {55000, 0.30, 4410}, {80000, 0.35, 7160}, {Double.MAX_VALUE, 0.45, 15160} }; public static SalaryResult calc(double base, double bonus, double deduction) { double gross base bonus - deduction; if (gross 0) gross 0; double social round2(gross * SOCIAL_RATE); double taxable gross - social - TAX_THRESHOLD; double tax 0; if (taxable 0) { for (double[] row : TAX_TABLE) { if (taxable row[0]) { tax round2(taxable * row[1] - row[2]); break; } } } double real round2(gross - social - tax); return new SalaryResult(gross, social, tax, real); } private static double round2(double v) { return Math.round(v * 100.0) / 100.0; } }逻辑说明TAX_TABLE按级距从小到大排列找到第一个上限大于等于应纳税所得额的档位就停。round2保证每步都保留两位小数避免最终实发出现分位误差。参数说明SOCIAL_RATE各地不同北京个人社保约 10.5%上海约 10.5%按实际改。TAX_THRESHOLD目前是 5000如果政策调整只改这一个常量。deduction是考勤扣款传正数表示扣减。4.2 批量生成工资记录一个事务包住所有插入月底给全公司生成工资记录时必须用事务。中途任何一条失败整批回滚否则会出现“张三有记录、李四没有”的脏数据。public void batchGenerate(String month) throws SQLException { String queryEmp SELECT emp_id, base_salary FROM employee WHERE status1; String insert INSERT INTO salary_record(emp_id,salary_month,base,bonus,deduction,social_security,real_salary) VALUES(?,?,?,?,?,?,?); Connection conn null; try { conn DBUtil.getConn(); conn.setAutoCommit(false); // 开启事务 try (PreparedStatement psQuery conn.prepareStatement(queryEmp); ResultSet rs psQuery.executeQuery(); PreparedStatement psInsert conn.prepareStatement(insert)) { while (rs.next()) { int empId rs.getInt(emp_id); double base rs.getDouble(base_salary); SalaryResult r SalaryCalculator.calc(base, 0, 0); psInsert.setInt(1, empId); psInsert.setString(2, month); psInsert.setDouble(3, base); psInsert.setDouble(4, 0); psInsert.setDouble(5, 0); psInsert.setDouble(6, r.social); psInsert.setDouble(7, r.real); psInsert.addBatch(); } psInsert.executeBatch(); } conn.commit(); } catch (SQLException ex) { if (conn ! null) conn.rollback(); throw ex; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } }逻辑说明setAutoCommit(false)后所有插入进入同一事务executeBatch()一次性提交比逐条插入快 5 到 10 倍。rollback()放在 catch 里保证异常时数据回到操作前。参数说明addBatch()不要加太多一般 500 条一批太多会占内存。month格式必须是yyyy-MM和表里的CHAR(7)对齐。如果插入时报Duplicate entry说明该月已生成过先删旧记录再重跑。5. 避坑与排查连不上库、乱码、打包后失效5.1 现象程序在 IDE 里正常打成 jar 后报 ClassNotFound原因MySQL 驱动 jar 没有一起打包或者MANIFEST.MF里没写Class-Path。解决用 Maven 的maven-shade-plugin打成 fat jar把mysql-connector-java和HikariCP都包含进去。检查pom.xml里驱动版本8.x 用com.mysql.cj.jdbc.Driver5.x 用com.mysql.jdbc.Driver写错会报No suitable driver。5.2 现象中文姓名存进数据库变成问号原因连接串没加characterEncodingutf8或者数据库、表、字段三层字符集不统一。解决连接串加characterEncodingutf8建库时指定utf8mb4建表时也指定utf8mb4。已经建错的表用ALTER TABLE employee CONVERT TO CHARACTER SET utf8mb4;修复。5.3 现象工资列表查询越来越慢翻到第 10 页要等 3 秒原因salary_record表数据量大后SELECT * FROM salary_record WHERE salary_month?没有索引走全表扫描。解决在salary_month上建索引CREATE INDEX idx_month ON salary_record(salary_month);。如果还慢检查是不是在循环里反复调DBUtil.getConn()改成一次连接查完再关。5.4 现象点“生成工资”按钮后界面卡死进度条不动原因数据库操作写在了 EDT 里。解决把batchGenerate放进SwingWorker.doInBackground()在done()里刷新表格。如果批量插入本身慢可以在publish()里发进度process()里更新进度条。5.5 现象Linux 服务器上启动报ERROR 2002 (HY000)或mysql ssl连接错误原因MySQL 服务没启动或者连接串里useSSLtrue但服务器没配证书。解决先systemctl status mysqld确认服务在跑连接串改成useSSLfalse如果是远程库确认bind-address不是127.0.0.1以及用户有%主机权限。6. 进阶技巧用 Java POI 导出工资条 Word 并自动分页工资算完行政通常要一份能打印的工资条。用 Java POI 生成 Word 比生成 Excel 更接近打印格式而且能按员工分页。下面给一个最小可用示例。import org.apache.poi.xwpf.usermodel.*; import java.io.FileOutputStream; public class SalaryWordExporter { public static void export(ListSalaryRow rows, String path) throws Exception { XWPFDocument doc new XWPFDocument(); for (int i 0; i rows.size(); i) { SalaryRow r rows.get(i); XWPFParagraph p doc.createParagraph(); p.setAlignment(ParagraphAlignment.CENTER); XWPFRun run p.createRun(); run.setText(工资条 - r.getMonth()); run.setBold(true); run.setFontSize(14); XWPFTable table doc.createTable(4, 2); table.getRow(0).getCell(0).setText(姓名); table.getRow(0).getCell(1).setText(r.getName()); table.getRow(1).getCell(0).setText(基本工资); table.getRow(1).getCell(1).setText(String.valueOf(r.getBase())); table.getRow(2).getCell(0).setText(社保); table.getRow(2).getCell(1).setText(String.valueOf(r.getSocial())); table.getRow(3).getCell(0).setText(实发); table.getRow(3).getCell(1).setText(String.valueOf(r.getReal())); if (i rows.size() - 1) { doc.createParagraph().setPageBreak(true); // 分页 } } try (FileOutputStream out new FileOutputStream(path)) { doc.write(out); } } }逻辑说明每个员工生成一个标题加一张 4 行 2 列的表格最后一个人之前插入分页符保证打印时一人一页。setPageBreak(true)是 POI 里控制分页的标准做法。参数说明rows从数据库按salary_month查出来按emp_id排序。path建议用工资条_2024-06.docx这种带月份的命名避免覆盖。如果导出的 Word 打开提示“内容有问题”检查 POI 版本和ooxml-schemas是否匹配5.x 版本需要额外引入poi-ooxml-full。验证方法导出后打开 Word按CtrlEnd跳到最后一页确认页数和员工数一致再按CtrlP看打印预览确认每页只有一个人的工资条。如果发现某页有两个人的数据说明分页符插在了错误位置检查循环里的if条件。我自己做这类系统时习惯先把init.sql和DBUtil定死再动界面。因为界面改十遍都行表结构改一遍就要重跑所有测试数据。另外工资计算里的每个常量都抽成static final政策一变只改一处别散落在各个ActionListener里。希望帮到你。本文还有配套的精品资源点击获取