简介面向Java初学者的员工工资管理系统采用Java Swing搭建桌面界面、MySQL负责数据持久化实现了管理员与普通用户双角色体系覆盖员工信息增删改查、部门维护、工资标准设置、工资查询与统计等业务模块适合作为课程设计、毕业设计或自学项目参考。资源为zip压缩包共163个文件包括十六个Java源文件、三十个窗体定义、一百余个编译后的类文件、数据库脚本、可运行jar包及项目配置文件压缩后大小仅944KB结构清晰、导入方便。目前已有3332人学习下载。代码中界面与业务逻辑相对分开初学者可通过窗体文件与类文件对照学习Swing组件的使用、事件监听以及Java与MySQL的交互过程附带数据库脚本可快速还原环境便于在此基础上修改完善或扩展为其他信息管理系统。1. Java 员工工资管理系统一门课设凭什么能难住两届学生每年到了课程设计季我都能看到不少人在群里问同一类问题Java 课设选题选了员工工资管理系统代码写到一半发现角色权限乱成一团、数据库表结构改不动、Swing 界面和业务逻辑全塞在一个类里根本没法调试。这个选题看起来简单——无非是员工增删改查加工资设置可真做起来光是管理员和普通用户两套权限的切换就能劝退一大半人。这份由 Java Swing 和 MySQL 实现的员工工资管理系统恰好把这些问题都处理得比较干净管理员能管部门、管员工、设工资、看统计普通用户只能查自己的工资。代码复杂度对 Java 初学和做课程设计的在校生来说刚刚好既不是 Hello World 级别的玩具也不至于像企业级框架那样让人看不懂。这篇文章我就把它的结构、核心实现、部署步骤和容易踩的坑完整拆一遍。2. 系统结构与技术选型先搞懂这几个类分别干了什么拿到一份源码第一步不是急着双击运行而是先把它的包结构和类职责理清楚。这个项目的类名起得很直白AddEmployee、ModifyEmployee、DeleteEmployee 负责员工档案管理SalarySetting 和 SalarySearch 管工资设置与查询AddDepartment、DeleteDepartment、ModifyDepartment 管部门数据Statistics 做统计MainFrame 是主窗口。从命名就能看出来它没有用什么 MVC 框架而是典型的 Java Swing 事件驱动写法——每个业务动作对应一个窗口类或面板类类内部直接处理按钮事件和数据库操作。这种写法对初学者其实比 Spring Boot 那套更适合用来理解桌面应用的本质界面是窗口交互是监听器数据是 JDBC 查询结果。我一般建议拿到项目后先按「实体-界面-数据库操作」三层去归类这些类。员工和部门是实体层各种 Employee 和 Department 类是界面层而数据库连接和 CRUD 操作分散在各自的事件处理逻辑里。这个项目没有单独抽出 DAO 层这是它的简单之处也是后期维护时最容易翻车的地方——改一个表名可能要动七八个文件。2.1 MainFrame 主窗口所有功能的入口和角色分流点MainFrame 是整个系统的心脏登录成功后所有操作都从这里开始。它的核心职责有两个根据登录用户的角色动态加载可用菜单以及维护各个业务窗口的单例或实例引用。管理员进来能看到部门管理、员工管理、工资设置、统计报表这几个入口普通用户进来通常只能看到工资查询这一个入口。从实现角度来看MainFrame 里一般会有一个角色判断逻辑用一个 int 或 String 类型的字段保存当前登录用户角色。Java Swing 做界面跳转最常见的做法是 setVisible(false) 隐藏当前窗口再 new 出目标窗口。这里有一个值得注意的细节如果每次点菜单都 new 一个新的窗口实例内存里会堆积大量无引用对象虽然 JVM 会回收但快速切换时容易出现界面卡顿。更稳妥的做法是用单例模式或者提前在 MainFrame 里初始化好各个子窗口的引用切换时只做 setVisible 控制。2.2 数据库表设计员工、部门、工资设置之间的关联关系MySQL 数据库在这个项目里存了四类核心数据管理员和用户账号、部门信息、员工档案、工资设置。员工表和部门表通常通过 dept_id 外键关联工资设置表通过 employee_id 与员工表关联。这种设计是典型的第二范式结构——员工不直接存部门名称字符串而是存部门 ID查询时再 JOIN 出部门名。我拿到数据库文件后第一件事就是看表结构定义重点关注字段类型和是否允许为空。比如工资字段用 DECIMAL(10,2) 而不是 FLOAT是因为浮点数在计算工资总和时会产生精度误差。员工入职日期用 DATE 类型查询某个月入职的员工时直接 BETWEEN 比较就行。如果原表结构里这类细节做得好说明作者有基本的数据库功底反之则要自己动手调整。2.3 JDBC 连接与驱动加载为什么换台电脑就连不上数据库这个项目用的是传统 JDBC 连接 MySQLClass.forName 加载驱动、DriverManager.getConnection 拿连接、Statement 执行 SQL、ResultSet 遍历结果。这套写法在课程设计里出现频率最高但也是环境问题的高发区。最常遇到的坑是驱动版本和 MySQL 版本不匹配MySQL 5.x 用 com.mysql.jdbc.DriverMySQL 8.x 必须换成 com.mysql.cj.jdbc.Driver连接 URL 还要加上 useSSLfalse 和 serverTimezoneAsia/Shanghai否则直接报时区错误。如果项目里用的是 DBUtil 之类的工具类通常会把 url、username、password 写成静态常量。我个人的习惯是建议把数据库连接参数提取到 properties 配置文件里虽然课程设计不要求这么干但后续改库名、改密码会省很多事。另外每次操作完要关 Connection、Statement、ResultSet顺序是从里到外否则连接池会把连接耗尽跑几次查询后程序就假死了。3. 登录与前权限控制管理员和普通用户的两套逻辑登录模块是判断一个 Java 课程设计用心程度的试金石。很多项目只做了账号密码比对登录成功后所有功能全部开放角色区分形同虚设。这个系统的不同之处在于登录动作不只是查一条用户表记录后续整个主界面的菜单加载、操作按钮的可用状态、数据查询的范围全部由当前登录者的角色决定。管理员和普通用户的分工可以简单概括为管理员管全局能增删改查所有数据普通用户只能看和自身相关的数据。用工位上的话说这是垂直权限和水平权限的区别——垂直权限决定你能点哪些菜单水平权限决定你查出来的数据是哪几条。3.1 登录校验流程从文本框到数据库比对再到角色分流登录窗口的逻辑通常是这样用户输入账号密码点击登录按钮事件监听器里取出 JTextField 和 JPasswordField 的内容拼接 SQL 查询用户表。密码比对有两种常见做法一种是直接 SELECT 出来然后字符串 equals 比较另一种是把 WHERE 条件直接写进 SQL。前者更安全至少密码不会出现在 SQL 日志里。查出来的用户记录里有一个字段标识角色假设是 role 字段1 代表管理员0 代表普通用户。校验通过后有两种处理方式一种是在登录窗口里直接 new MainFrame(role)把角色传进去另一种是做全局静态变量保存当前登录用户。第一种更推荐显式传参比全局变量好理解也方便后期扩展多角色。需要注意的是 JPasswordField 的 getPassword() 返回的是 char[] 而不是 String很多初学者在这里直接用 toString() 拿到的是对象地址导致密码永远比对失败。3.2 菜单动态加载如何让不同角色看到不同功能入口菜单加载有两种实现路径。第一种是 JMenuBar 配合 JMenu 和 JMenuItem登录时根据角色逐个 setVisible 控制显示隐藏第二种是直接用 JButton 排列在主界面上同样按角色条件判断是否 add 到容器。这个项目整体是窗口跳转风格我估计用的是第二种居多因为功能都以独立窗口类存在主界面更像是功能选择台。动态加载菜单有一个原则不要只藏入口后端也要做拦截。虽然桌面应用没有 Web 应用那种严格的拦截器概念但至少在每个业务窗口的构造方法里可以再次校验角色。如果普通用户手工 new 一个管理员功能窗口出来系统不应该允许操作。这种双重校验在课程设计里属于加分项写进报告里也能体现你对权限控制有完整思考。3.3 参数传递与窗口切换为什么关闭子窗口后主界面也退了Java Swing 开发里有一个非常经典的问题——点击子窗口的关闭按钮整个程序都退出了。原因是子窗口调用了 System.exit(0) 或者 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)。正确做法是子窗口关闭时只 dispose() 自己主窗口通过 setVisible(true) 重新显示。如果项目里出现点击返回主菜单没反应多半是主窗口实例被垃圾回收了或者主窗口引用在子窗口里没有保存。窗口切换的另一种常见写法是用 CardLayout 做单窗口多面板切换好处是避免了多窗口实例的管理问题坏处是界面代码全部堆在一个类里类体积迅速膨胀。对于这个项目这种功能较多的系统保持多窗口结构反而更清晰每个业务类只负责自己的窗口逻辑。关键点在于主窗口统一管理子窗口实例子窗口保存主窗口引用关闭时通知主窗口刷新数据列表。4. 核心模块实现细节增删改查背后的边界条件处理员工工资管理系统看起来功能不少但归纳起来无非是部门管理、员工管理、工资设置、工资查询、统计报表这几块。每一块单独拎出来都不难难的是把边界条件处理到位。举个例子删除一个还在职且已有工资记录的员工是直接 DELETE 还是软删除删除一个还有员工的部门是拒绝删除还是连员工一起删这些细节决定了系统是能交作业的水平还是能实际用的水平。从源码类的命名来看AddEmployee、ModifyEmployee、DeleteEmployee 分了三个类而不是合并成一个 EmployeeManager这种方式类数量多一些但每个类职责单一事件监听器写在各自的类里互相之间不干扰。对于初学者来说这种拆分方式反而比一个大而全的 CRUD 类更容易读懂。4.1 员工档案的增删改必填校验和外部关联的影响添加员工时界面一般会有姓名、性别、年龄、部门、入职日期、基础工资等字段。点击确定按钮后先做前端校验姓名不能为空、工资必须是数字、部门必须已选择。如果在 JTextField 里输入了非数字内容直接入库数据库会报 NumberFormatException 或 SQL 异常对用户体验来说就是程序闪退。删除员工的逻辑要复杂一些。如果工资设置表里有这个员工的记录直接执行 DELETE FROM employee WHERE id ? 会因为外键约束失败而报错。常见做法是先查工资表有没有关联记录有的话先删除工资记录再删除员工或者把删除操作包在事务里执行。该项目用的是 MySQL InnoDB 引擎支持事务所以用 connection.setAutoCommit(false) 包住多条删除语句是合理的做法。我个人在处理这类问题时习惯先查关联再做决定如果是管理员删除员工操作界面应弹出确认框告知该员工有关联工资记录。修改员工信息的核心不是 UPDATE 语句怎么写而是保存之前的数据回显。ModifyEmployee 类的关键逻辑是先从列表选中一行把该行数据填充到各个输入框里用户改完后再提交。这里有一个容易出错的地方主键 ID 要单独保存不能随着修改被覆盖否则 UPDATE 的 WHERE 条件会失效导致所有员工数据全被改成同一条。4.2 工资设置与查询一条 SQL 搞定还是分步查询工资设置这个功能模块的工作流通常是管理员选择一个员工录入基本工资、岗位工资、绩效工资等系统计算出应发工资并保存。有的项目会把五险一金、个税起征点也算进去复杂度和业务要求直接相关。从类名 SalarySetting 来看这个项目的工资条目应该是预设好的固定字段不是自定义工资项。工资查询模块相对简单普通用户登录后输入工号或直接列出自己的工资记录。查询条件常见的有按月份、按姓名、按部门。SQL 写法从简单到复杂依次是SELECT * FROM salary WHERE employee_id ?、再加月份 BETWEEN、再加 JOIN employee 表做部门过滤。性能上这个量级的数据没有任何压力重点是查询条件为空时的处理——如果用户一个条件都没填应该默认查出所有记录而不是拼接出一条语法错误的 SQL。4.3 统计报表模块用 Java 代码做聚合还是交给 SQLStatistics 类通常负责两类统计各部门员工人数、各部门工资总额。如果只做数字展示用 SQL 的 GROUP BY 加聚合函数是最省事的。查询部门工资总额可以写成SELECT d.dept_name, SUM(s.salary) AS total_salary FROM salary s JOIN employee e ON s.employee_id e.id JOIN department d ON e.dept_id d.id GROUP BY d.dept_id这段 SQL 的作用是先把工资表、员工表、部门表三张表通过外键关联起来再按部门分组用 SUM 算出每个部门的工资总和。如果项目里没有单独做图表展示结果通常会放在 JTable 里呈现。JTable 的填充方式是把 ResultSet 遍历出来的数据放进二维数组再配合 String[] 类型的列名数组构造表格模型。如果原项目需求里要求做柱状图或饼图常见做法是引入 JFreeChart 依赖。课程设计里用 JFreeChart 是很讨巧的加分项——不用自己画坐标轴几行代码就能生成一张图表。但要注意 JFreeChart 的版本兼容问题较新版本要求 JDK 11 以上如果你的环境是 JDK 8要选择 1.5.x 的旧版本。5. 部署运行与常见问题排查从 JDK 环境到数据库连接的五个坑课程设计项目拿到手能不能跑起来很大程度取决于环境配置。这块我在帮人调试时见过太多翻车现场了归纳下来高频问题集中在这五个方面每一条都至少让我浪费过半小时。5.1 现象一双击运行没反应或报找不到主类运行 Java Swing 程序前要确认 JDK 装好了、环境变量配对了。命令行输入 java -version 能输出版本号是基本前提。很多初学者在 IDE 里能跑但导出成 jar 包后双击没反应是因为 jar 包没有指定 Main-Class 清单属性。解决办法是在 IDE 的打包配置里设置主类为 MainFrame或者用命令行打包时在 MANIFEST.MF 里写明 Main-Class。5.2 现象二JDBC 连接报 ClassNotFoundException这个异常百分之九十是驱动 jar 包没有加入到项目的构建路径。如果用的是 IDE右键项目 → Build Path → Add External JARs选择对应版本的 mysql-connector-java 包。还有一种是 jar 包加进去了但版本不对MySQL 8.x 的驱动类名和 5.x 不同检查一下 Class.forName 里的字符串和 jar 包版本是否匹配。5.3 现象三SQLException: Unknown database连接 URL 里写的数据库名在 MySQL 里不存在。比如 URL 写的是 jdbc:mysql://localhost:3306/salary_system但导入的 SQL 脚本建的是 salary 库那必然报错。解决方法是登录 MySQL 后用 SHOW DATABASES 查看实际库名或者在 IDE 里先手动执行一遍 SQL 脚本确认建库语句执行成功。5.4 现象四中文乱码界面和数据库全是问号乱码问题根源在于字符集不一致。MySQL 建库时如果没指定 utf8mb4默认可能用 latin1。正确的建库语句是 CREATE DATABASE IF NOT EXISTS xxx DEFAULT CHARSET utf8mb4。同时连接 URL 里追加 characterEncodingutf8 参数两边都设为 UTF-8问题基本能解决。项目源码文件本身也可能因为编码不一致而乱码IDE 右下角可以切换文件编码GBK 和 UTF-8 之间转换一下就能看到正常中文。5.5 现象五连接超时或 Access denied for userAccess denied 是账号密码错误或权限不足。MySQL 8.x 默认的认证插件是 caching_sha2_password老版本的 JDBC 驱动不认识这个插件会报错。解决办法是新建一个用户并指定 mysql_native_password 认证方式或者把 root 用户的认证方式改掉。如果只是本机学习用ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码 就行。连接超时通常检查一下 MySQL 服务是否启动了Windows 下在服务管理器里看 MySQL 的服务状态。6. 进阶给统计模块加一个按部门汇总的表格展示基础功能跑通之后如果想让这个项目在答辩时更有说服力我建议把统计模块从单纯的数字展示升级成带汇总效果的表格。这个改造不需要改数据库结构只加一个查询方法和一个展示面板。第一步是在 Statistics 类里新增一个方法查询各部门的员工人数与平均工资。SQL 可以用 GROUP BY 一次算出来SELECT d.dept_name, COUNT(e.id) AS emp_count, AVG(s.total_salary) AS avg_salary FROM department d LEFT JOIN employee e ON d.dept_id e.dept_id LEFT JOIN salary s ON e.id s.employee_id GROUP BY d.dept_id这里用 LEFT JOIN 而不是 INNER JOIN是为了让没有员工的空部门也能显示出来人数为 0。如果只统计有员工的部门用 INNER JOIN 即可两种写法在数据量小的时候性能没有差别但从语义上讲统计报表通常要把全部门都列出来才完整。第二步是把查询结果填充到 JTable。JTable 的构造需要两个核心参数列名数组和数据二维数组。数据数组的行数对应结果集的行数列数对应 SELECT 出来的字段个数。遍历 ResultSet 时用 List 收集每一行的数据最后转换成二维数组传给表格模型。考虑到 JTable 默认不支持按列排序而答辩时老师很可能会问「能不能按工资总额从高到低排」可以直接在 SQL 末尾加 ORDER BY total_salary DESC比在 Java 代码里写 Comparator 简单得多。如果后面想加图表展示JFreeChart 的 BarChart 只需要把同样的统计结果转成 DefaultCategoryDataset 就行。这个项目本身的代码结构是足够干净的类名即功能数据库表关系也清晰花一个晚上通读源码、把五个坑对照排查一遍基本就能运行起来。从那以后我每次拿到这类课设项目都强制自己先走一遍「建库 → 配驱动 → 跑登录 → 逐模块点一遍」的流程在 IDE 里把控制台的异常信息截图存下来再动手改代码。这种习惯帮我省掉了大量重复排错的时间希望也能帮到你。本文还有配套的精品资源点击获取