当前位置: 首页 > news >正文

Java课程设计实战:员工工资管理系统V3完整实现

简介:这是一套面向计算机专业本科生的Java课程设计级工资管理实战项目,聚焦Swing桌面应用开发与MySQL数据库交互能力训练。系统完整实现双角色权限控制:员工可登录查询个人薪资,管理员支持全员薪资浏览、增删改等核心CRUD操作,覆盖GUI界面设计、JDBC连接、SQL事务处理等关键知识点。压缩包含46个文件,主体为21个编译后class文件与10个源码java文件(含主程序入口、DAO层、UI组件类),辅以5个XML配置文件(用于界面布局与数据映射)、2个MySQL驱动jar包及初始化用salary.sql脚本,整体4.33MB,结构清晰便于模块化学习。已有157人下载学习,提供可直接导入IDEA或Eclipse运行的完整工程,含数据库建表语句与典型测试数据,适合作为课程作业参考、期末项目模板或Swing+MySQL技术栈入门实践范例。 很多同学做Java课程设计的时候,十个里头八个都会选“员工管理系统”,但交上去的作业往往都长一个样:一个登录页、一个表格、几个增删改查按钮,老师翻两页就关了。这套员工工资管理系统V3,是我在带了几轮课设、帮不少学弟学妹改完代码之后沉淀出来的版本。它跟普通课设最大的区别在于,把“工资”这件事真正做成了业务流程——员工档案进来,考勤数据跟着进来,工资按考勤、加班、社保公积金一路算到底,最后导出的Excel能直接给财务用,而不是把几个字段塞进数据库就算完事。

这篇文章适合三类人看:第一类是正在做Java课程设计、被要求写桌面应用的同学,你能从里面找到完整的数据库设计、Swing界面搭建思路、JDBC连接方案,照着敲一遍就能跑;第二类是代码写完了但感觉没亮点、答辩怕被问住的人,后面专门有一章讲老师爱问哪些问题、怎么把加分项写进去;第三类是接手别人代码、需要快速看懂Swing + MySQL项目结构的,我尽量把每个设计决策背后的原因讲明白,方便你改别人的代码时动手有底。

1. 系统整体设计与技术选型

做课设第一步不是写代码,是先想清楚“我为什么要这么做”。这个系统在技术选型上就花了不少心思,它不是随便找个框架拼出来的,而是综合考虑了课程评分标准、代码量可控性、演示效果这些因素,最后定下了Java + Swing + MySQL的组合。

1.1 为什么是Swing而不是SpringBoot

很多同学一上来就问:现在公司里都用SpringBoot,为什么课设还让我写Swing?这个问题的答案其实就藏在课程设计的评分标准里。课程设计的核心考核点是“你是否掌握了Java基础”,包括面向对象思想、事件监听机制、集合框架、异常处理、GUI布局等等。Swing桌面程序打开以后,老师能直接看到界面、点按钮、录入数据、看计算结果,整个交互过程是连续、直观的,演示效果天然比网页好。

SpringBoot不是不好,而是对这个场景来说“过重”了。它需要额外配置Spring容器、整合持久层框架、设计REST接口,还要配合前端页面使用,课设周期往往只有几周,光是环境搭建和框架理解就能消耗掉大量时间。更实际的问题是,学完Java基础课的同学对SpringBoot的依赖注入、自动配置普遍理解不深,答辩时老师一问底层原理,很容易露馅。Swing则不同,它逼着你把面向对象、事件监听、匿名内部类、继承封装这些基础概念用出来,哪怕代码写得不够优雅,但你至少能讲清楚每一行是在干什么。

选MySQL也是一个务实的决策。MySQL在学生群体里普及率最高,安装配置简单,图形化工具Navicat、Workbench都顺手,网上遇到问题一搜就是答案。相比Oracle太重、SQL Server在Windows下虽然好装但答辩环境不一定有,MySQL跨平台、体积小、资料多,是课设最稳妥的选择。数据库用MySQL还有一个隐藏好处:老师问“JDBC连接怎么写”这种基础问题时,MySQL的驱动、连接串写法是最标准的教学案例。

所以这个选型组合的逻辑很清楚:用Swing保证界面可见、代码基础扎实;用MySQL保证环境通用、操作简单;用JDBC直连保证技术栈集中、没有多余框架干扰。V1,V2到V3的演进也是在选型不变的前提下,通过功能叠加来做深度,而不是反复换框架。

1.2 功能模块怎么拆,才能让老师一眼看到工作量

普通课设的员工管理系统只有员工表,加个简单工资字段就完事了。V3这个版本我重新拆了功能,目标很简单:让业务链闭环。你录入的每一份考勤数据,最终要能影响工资计算结果;你算出来的每一份工资,最终要能汇总成报表并导出。老师演示的时候,从入职录入到工资发放,一整条流程走下来,工作量一目了然。

模块拆成了这样六个部分:

  • 登录认证模块:账号密码登录,区分管理员权限,登录后主界面显示当前用户。
  • 员工档案管理模块:员工的增删改查、按工号或姓名搜索、部门筛选,支持分页显示。
  • 考勤管理模块:按员工按月录入出勤天数、加班小时、缺勤天数,月底统一汇总。
  • 工资管理模块:根据考勤和基础工资计算出应发、实发,支持批量生成某月工资,支持查询历史工资明细。
  • 报表统计模块:按月汇总各部门工资总额、平均工资,导出CSV文件。
  • 系统设置模块:修改密码、查看社保公积金比例配置。

这里想强调一个点:模块不是越多越好,关键在于每个模块之间有没有数据关联。有的同学习惯把员工管理和工资管理做成两个完全独立的窗口,员工表和工资表之间只靠一个员工ID连着,没有任何联动操作,这其实就是两个CRUD拼在一起,在老师眼里和一个CRUD没有本质区别。V3的做法是把考勤数据和工资计算结果串起来,工资核算的时候去读考勤表的数据,考勤数据不完整就提示用户补录,这样整个系统的“话题性”就出来了,答辩时老师一看就知道你业务逻辑是通的。

2. 数据库设计:工资系统最核心的部分

很多课设失败的起点都是数据库表设计得太随意。员工、部门、工资一股脑塞进一张表里,字段全用String糊弄,算工资的时候才发现缺这缺那。我重构V3时把整个表结构调整了一遍,下面这些表结构直接可以用在你的项目里,字段命名和注释我都写好了。

2.1 六张核心表的字段设计

整个系统一共六张表,它们的关系是这样的:admin_user管登录,department管组织架构,employee管员工档案,attendance管考勤,salary_detail管工资流水,salary_config管社保公积金等比例配置。

先看建表SQL,我加了比较详细的注释:

CREATE DATABASE IF NOT EXISTS salary_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE salary_db; -- 管理员表 CREATE TABLE admin_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录用户名', password VARCHAR(64) NOT NULL COMMENT '登录密码', real_name VARCHAR(50) COMMENT '真实姓名', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT '管理员账号表'; -- 部门表 CREATE TABLE department ( dept_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL UNIQUE COMMENT '部门名称' ) COMMENT '部门表'; -- 员工表 CREATE TABLE employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT '工号,如E001', emp_name VARCHAR(50) NOT NULL COMMENT '姓名', gender CHAR(1) DEFAULT '男' COMMENT '性别', dept_id INT NOT NULL COMMENT '所属部门', position VARCHAR(50) NOT NULL COMMENT '岗位', base_salary DECIMAL(10,2) NOT NULL COMMENT '基本工资', hire_date DATE COMMENT '入职日期', phone VARCHAR(20) COMMENT '联系电话', status TINYINT DEFAULT 1 COMMENT '1在职 0离职', FOREIGN KEY (dept_id) REFERENCES department(dept_id) ) COMMENT '员工档案表'; -- 考勤表,按月记录每人出勤情况 CREATE TABLE attendance ( att_id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL COMMENT '员工ID', att_month VARCHAR(7) NOT NULL COMMENT '考勤月份,格式2025-01', normal_days INT DEFAULT 0 COMMENT '正常出勤天数', late_days INT DEFAULT 0 COMMENT '迟到天数', absent_days INT DEFAULT 0 COMMENT '缺勤天数', leave_days INT DEFAULT 0 COMMENT '请假天数', overtime_hours DECIMAL(5,1) DEFAULT 0 COMMENT '加班小时', UNIQUE KEY uk_emp_month (emp_id, att_month), FOREIGN KEY (emp_id) REFERENCES employee(emp_id) ) COMMENT '考勤表'; -- 工资配置表,存社保比例、公积金比例、加班倍数等 CREATE TABLE salary_config ( config_id INT PRIMARY KEY AUTO_INCREMENT, config_name VARCHAR(50) NOT NULL COMMENT '配置项名称', config_value DECIMAL(10,4) NOT NULL COMMENT '配置值', remark VARCHAR(100) COMMENT '说明' ) COMMENT '工资计算参数表'; -- 工资明细流水表 CREATE TABLE salary_detail ( salary_id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL COMMENT '员工ID', month VARCHAR(7) NOT NULL COMMENT '工资月份', base_salary DECIMAL(10,2) NOT NULL COMMENT '基础工资', overtime_pay DECIMAL(10,2) DEFAULT 0 COMMENT '加班费', bonus DECIMAL(10,2) DEFAULT 0 COMMENT '奖金', absent_deduct DECIMAL(10,2) DEFAULT 0 COMMENT '缺勤扣款', social_security DECIMAL(10,2) DEFAULT 0 COMMENT '社保扣款', housing_fund DECIMAL(10,2) DEFAULT 0 COMMENT '公积金扣款', gross_salary DECIMAL(10,2) DEFAULT 0 COMMENT '应发工资', net_salary DECIMAL(10,2) DEFAULT 0 COMMENT '实发工资', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_emp_month_salary (emp_id, month), FOREIGN KEY (emp_id) REFERENCES employee(emp_id) ) COMMENT '工资明细表';

有几个字段设计上的坑值得说一下。第一个是金额字段,工资相关的所有金额都用DECIMAL(10,2),绝对不要用Float或Double。原因很简单,浮点数在计算机里是二进制近似存储的,比如0.1加0.2会得到0.30000000000000004,工资算错一分钱都是大事故。DECIMAL是定点数,能精确到分,查出来是什么就显示什么。这也是答辩时老师几乎必问的问题,你回答“用DECIMAL是为了避免浮点精度损失”,这个印象分就拿到了。

第二个是唯一约束。考勤表和工资明细表都加了 (emp_id, month) 的联合唯一键,意义在于防止同一个人同一个月被重复录入两条考勤、重复生成两份工资。这是一个很实在的防错设计,界面层判断再多也有漏网的时候,数据库约束是最后一道防线。

第三个是状态字段。employee表里有一个status字段,用TINYINT表示在职/离职。做删除功能的时候,我建议用逻辑删除而不是物理删除——员工离职了,他的历史工资记录还要保留,不能把数据物理删掉,所以员工管理里的“删除”实际是执行UPDATE status = 0,而不是DELETE。这种设计在真实系统里叫软删除,课设里用了会很加分。

2.2 为什么工资明细要单独建表而不是在员工表加字段

这是V3重构时很重要的一个设计决策。V1版本的做法是在employee表里直接加一个“上月工资”的字段,后来发现完全行不通:员工每个月工资都不同,如果直接在员工表里加字段,那要么只能存最新一个月,历史记录全部丢失,要么就建一堆salary_202501、salary_202502这种后缀表,显然不是正经做法。

正确的方案是单独建一张salary_detail流水表,把员工ID + 月份做成联合唯一键,每个月插入一条独立记录。工资不是“员工的一个属性”,而是“按月产生的一条流水”。员工表的base_salary是算工资的基数,工资明细表里的base_salary是那个月实际发放的基数,两者可能不同——比如某员工年中调薪了,6月之前的工资按旧基数算,7月之后按新基数算,只有通过流水表才能保留这个历史轨迹。

这种“主数据 + 流水数据”的思想在真实系统里非常普遍,考勤表也是一样的道理。理解了这个,你的数据库设计水平就超过大部分课设同学了。

2.3 初始化数据的准备

表建好之后,记得往配置表和部门表里插入基础数据,不然界面里下拉框全是空的:

INSERT INTO department (dept_name) VALUES ('技术部'), ('市场部'), ('人事部'), ('财务部'); INSERT INTO salary_config (config_name, config_value, remark) VALUES ('社保比例', 0.08, '个人缴纳部分按基本工资8%'), ('公积金比例', 0.12, '个人缴纳部分按基本工资12%'), ('加班倍数', 1.5, '工作日加班按1.5倍工资'), ('月计薪天数', 21.75, '法定月平均计薪天数'), ('日标准工时', 8, '每天标准工作小时'); INSERT INTO admin_user (username, password, real_name) VALUES ('admin', 'admin123', '系统管理员');

这里我想多说一句:把社保比例、公积金比例、加班倍数这些参数抽到一张配置表里,比硬编码在Java代码里好得多。因为比例是会变的,万一哪天社保从8%改成10%,配置表里改一条数据就完事,不用改代码重新编译。这个设计属于“业务参数配置化”,在真实系统里是基本要求,课设里能主动做出来,很大程度上体现了你对系统可维护性的理解。

3. 核心代码实现:从登录到工资计算

讲完设计就要落地了。这一章我按代码层次来拆解,从最底层的JDBC工具类,到Swing界面,再到业务逻辑,每一块都给出核心代码和关键思路,你可以直接照着敲。

3.1 JDBC工具类:一切数据库操作的地基

整个系统所有DAO层都要用到数据库连接,所以我先写一个统一的DBUtil类,把所有连接的创建和资源释放收拢到一起:

package util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/salary_db" + "?useUnicode=true&characterEncoding=UTF-8" + "&useSSL=false&serverTimezone=Asia/Shanghai" + "&allowPublicKeyRetrieval=true"; private static final String USER = "root"; private static final String PASSWORD = "你自己的密码"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError("MySQL驱动加载失败,请检查依赖"); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

这段代码里有几个细节值得展开。URL里那一长串参数,每个都不是随便加的。characterEncoding=UTF-8解决中文乱码;serverTimezone=Asia/Shanghai解决MySQL 8.x版本出现的时区报错;useSSL=false避免连接时SSL握手警告;allowPublicKeyRetrieval=true解决MySQL 8.x使用caching_sha2_password加密方式时可能出现的“Public Key Retrieval is not allowed”问题。

驱动类也需要注意版本匹配。MySQL Connector/J 5.x版本用Class.forName("com.mysql.jdbc.Driver"),8.x版本用Class.forName("com.mysql.cj.jdbc.Driver")。很多同学驱动包版本换了,代码里的类名没换,报ClassNotFoundException之后卡半天,其实就这一个原因。在课设阶段,资源释放也不能偷懒,每次使用完Connection、PreparedStatement、ResultSet都要关闭,否则MySQL连接会一直占用,连接数超了系统就崩了。

3.2 Swing主界面:左侧菜单 + 右侧内容卡片

登录这块我就不贴完整代码了,就是一个JFrame + 两个文本框 + 一个按钮,点击按钮后去admin_user表查用户名密码是否匹配,匹配就打开主窗体,不匹配就弹提示。真正考验布局能力的是登录之后的主界面。

V3的主界面用的是左侧菜单 + 右侧内容区的经典布局。左侧放一个JList作为功能菜单,右侧放一个JPanel,使用CardLayout来切换不同的功能面板:

public class MainFrame extends JFrame { private final CardLayout cardLayout = new CardLayout(); private final JPanel contentPanel = new JPanel(cardLayout); public MainFrame(String username) { setTitle("员工工资管理系统 V3 - 当前用户:" + username); setSize(1100, 720); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); String[] menuItems = {"员工管理", "考勤管理", "工资管理", "报表统计", "系统设置"}; JList<String> menuList = new JList<>(menuItems); menuList.setFont(new Font("微软雅黑", Font.PLAIN, 14)); menuList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); contentPanel.add(new EmployeePanel(), "员工管理"); contentPanel.add(new AttendancePanel(), "考勤管理"); contentPanel.add(new SalaryPanel(), "工资管理"); contentPanel.add(new ReportPanel(), "报表统计"); contentPanel.add(new SettingsPanel(), "系统设置"); menuList.addListSelectionListener(e -> { if (!e.getValueIsAdjusting()) { cardLayout.show(contentPanel, menuList.getSelectedValue()); } }); getContentPane().add(new JScrollPane(menuList), BorderLayout.WEST); getContentPane().add(contentPanel, BorderLayout.CENTER); } }

CardLayout的好处是界面切换时不会重新创建Panel,每个Panel只需要初始化一次,切换成本极低,同时各个功能模块之间代码也隔离得干净。菜单用JList而不是JButton数组,是为了将来加菜单项方便,而且JList自带选中高亮效果,操作更直观。

每个功能面板我建议继承JPanel,自己管理内部的布局和数据刷新逻辑。比如EmployeePanel里放一个查询条件区、一个工具栏(增删改按钮)、一个表格分页区;SalaryPanel里放月份选择器、员工选择下拉框、工资计算按钮和工资明细表格。这样每个Panel的职责单一,代码量虽然大,但查起bug来很清楚。

3.3 员工管理模块:表格分页与下拉框筛选

员工管理是最基础的功能,V3在这里加了两个亮点:分页查询和多条件筛选。分页用MySQL的LIMIT语法,查询思路是先用COUNT查询总条数,再按当前页码取数据:

public List<Employee> findPage(String keyword, int deptId, int pageNum, int pageSize) { StringBuilder sql = new StringBuilder(); sql.append("SELECT e.emp_id, e.emp_no, e.emp_name, e.gender, d.dept_name, ") .append("e.position, e.base_salary, e.hire_date, e.phone ") .append("FROM employee e LEFT JOIN department d ON e.dept_id = d.dept_id ") .append("WHERE e.status = 1 "); List<Object> params = new ArrayList<>(); if (keyword != null && !keyword.trim().isEmpty()) { sql.append("AND (e.emp_no LIKE ? OR e.emp_name LIKE ?) "); params.add("%" + keyword + "%"); params.add("%" + keyword + "%"); } if (deptId > 0) { sql.append("AND e.dept_id = ? "); params.add(deptId); } sql.append("ORDER BY e.emp_id LIMIT ?, ?"); params.add((pageNum - 1) * pageSize); params.add(pageSize); // 执行查询,映射到Employee对象列表 }

这里有一个很实用的SQL技巧:模糊查询用LIKE ?,参数里手动拼接%号,而不是直接拼SQL字符串。之前的写法是用"%" + keyword + "%"直接拼进去,这种写法有SQL注入风险,比如keyword里输入一个单引号就能破坏SQL结构。用PreparedStatement占位符就能让参数和SQL结构彻底分离,保证安全。

界面侧,JTable配DefaultTableModel是最常见的组合。我遇到不少同学在这个环节被卡住——数据库数据查出来了,但表格显示不出来。原因通常是忘了调用tableModel.setRowCount(0)清空旧数据,或者列名和数据字段对不上。我的习惯是先定义好表格的列名数组,每次加载数据时先setRowCount(0)清空,再逐行addRow,这样刷新逻辑不会出问题。

部门筛选下拉框我也做了细节处理:下拉框里加了一个“全部部门”选项,value是0,这样与前端的筛选条件能自然对接,不用单独判断“用户没选部门怎么办”。这个逻辑虽然简单,但很多同学的代码里就是因为少了这个“全部”选项,导致筛选和取消筛选的成本很高,用户体验很差。

3.4 工资计算逻辑:最体现业务深度的地方

工资计算是整个系统的核心,也是V3版本能拿高分的关键。工资计算的业务规则是这样的:

  • 日工资 = 基本工资 ÷ 21.75(月计薪天数,法定标准)
  • 小时工资 = 日工资 ÷ 8(日标准工时)
  • 加班费 = 小时工资 × 加班小时 × 加班倍数(1.5)
  • 缺勤扣款 = 日工资 × 缺勤天数
  • 应发工资 = 基本工资 + 加班费 + 奖金 - 缺勤扣款
  • 社保扣款 = 基本工资 × 社保比例(0.08)
  • 公积金扣款 = 基本工资 × 公积金比例(0.12)
  • 实发工资 = 应发工资 - 社保扣款 - 公积金扣款

这些规则我抽成了一个独立的工具类,跟界面完全解耦:

package service; import model.SalaryResult; public class SalaryCalculator { public static SalaryResult calculate(double baseSalary, double bonus, int absentDays, double overtimeHours, double socialSecurityRatio, double housingFundRatio, double overtimeMultiple) { double dailySalary = baseSalary / 21.75; double hourlySalary = dailySalary / 8; double overtimePay = hourlySalary * overtimeHours * overtimeMultiple; double absentDeduct = dailySalary * absentDays; double grossSalary = baseSalary + overtimePay + bonus - absentDeduct; double socialSecurity = baseSalary * socialSecurityRatio; double housingFund = baseSalary * housingFundRatio; double netSalary = grossSalary - socialSecurity - housingFund; return new SalaryResult(overtimePay, absentDeduct, socialSecurity, housingFund, grossSalary, netSalary); } }

我特意把这个类设计成static方法,不依赖任何界面状态,是因为这样写有个好处:单元测试可以直接调用,不用启动Swing界面,也不用构造一堆对象。答辩的时候,你可以给老师现场算一遍数字,比如输入基本工资8000、加班10小时、缺勤1天,能快速得出结果,这种“当场验证”的演示效果比口头讲一万句都管用。

工资生成模块还有一个细节:批量生成时一定要用事务。比如要给技术部20个人生成这个月的工资,如果循环里第5个人的数据插入了,第6个人因为考勤数据缺失插入失败,那么第5个人的工资就已经写进库了,数据就是一半成功一半失败,月份一核算就乱。所以批量生成的正确做法是,开启事务、逐条计算并插入、全部成功再提交、任何一条失败就整体回滚:

Connection conn = DBUtil.getConnection(); try { conn.setAutoCommit(false); for (Employee emp : employeeList) { // 查询考勤、计算工资、insert salary_detail // ... insertSalaryDetail(conn, emp, result); } conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); }

这个事务控制就是老师最喜欢追问的点。“如果算到一半有一个员工数据不对,你们怎么处理?”把这段代码往上一摆,答“我用事务保证要么全部成功要么全部回滚”,这个问题的分数就拿到了。

3.5 报表导出:CSV文件其实比Excel更适合课设

报表模块里,最常见需求是把工资明细导出来给老师看。很多同学第一反应是用Apache POI写Excel,结果引入一堆jar包,版本冲突搞半天。其实在课设场景下,导CSV文件是更快更稳的选择。CSV本质上就是带分隔符的纯文本文件,Excel直接能打开,而且不需要引入任何第三方库,用Java自带的BufferedWriter就能写:

public void exportSalaryCsv(String month, List<SalaryDetail> list) throws IOException { try (BufferedWriter writer = Files.newBufferedWriter( Paths.get("salary_" + month + ".csv"), StandardCharsets.UTF_8)) { writer.write("\uFEFF"); // 写入BOM,防止Excel打开乱码 writer.write("工号,姓名,部门,基本工资,加班费,应发工资,实发工资\n"); for (SalaryDetail d : list) { writer.write(String.join(",", d.getEmpNo(), d.getEmpName(), d.getDeptName(), d.getBaseSalary().toString(), d.getOvertimePay().toString(), d.getGrossSalary().toString(), d.getNetSalary().toString()) + "\n"); } } }

注意我特意写了一个\uFEFF开头的BOM字符。这是很多CSV导出代码容易踩的坑——用Excel直接打开导出的CSV文件时,中文全部变成乱码,就是因为文件缺少UTF-8 BOM标记。加上这个标记之后,Excel就能正确识别编码。这个小细节在演示的时候特别出彩,老师看到你的导出文件干干净净,比看到一堆报错强得多。

4. 常见问题与排查技巧实录

写课设不可能不踩坑,下面这些是我在V1到V3迭代过程中真实遇到过的典型问题,全部整理成速查表,每一条都是踩过坑换来的经验。

问题现象根本原因解决办法
运行时报ClassNotFoundException: com.mysql.cj.jdbc.Driver没有把MySQL驱动jar包引入项目确认jar包已放到lib目录并右键Add as Library
报错Unknown Server Time ZoneJDBC URL缺少时区参数在URL末尾加serverTimezone=Asia/Shanghai
SQL中文字符变成???数据库表字符集或连接字符集不对建表用utf8mb4,URL加characterEncoding=UTF-8
Excel打开CSV文件中文乱码文件没有带BOM头写入前先输出\uFEFF
窗口点击按钮后卡死、无响应数据库查询放到了Swing事件线程上执行改用SwingWorker或线程池执行耗时操作
工资金额出现0.30000000000000004使用了浮点类型字段所有金额改用DECIMAL,Java侧用BigDecimal
同一个月重复生成工资缺少唯一约束表加UNIQUE KEY (emp_id, month)

4.1 中文乱码问题

Swing界面中文乱码通常有三个来源。第一个是IntelliJ IDEA控制台乱码,这个最隐蔽,因为界面里显示正常,但输出的日志是乱码,排查半天才发现是IDEA自身的编码问题。解决办法是打开Help > Edit Custom VM Options,加上一行-Dfile.encoding=UTF-8然后重启IDEA。

第二个来源是MySQL连接串没加编码参数。URL必须以characterEncoding=UTF-8结尾,否则从数据库读出来的中文字符串就是乱码。注意这里的参数名大小写也不能错,我见过写成charSet=utf8这种歪写法,在部分版本的驱动里是不生效的。

第三个来源是建表时字符集选错了。表或字段的字符集默认是latin1的话,就算连接串对了也白搭。最稳妥的做法是建库时就指定utf8mb4,如上文SQL那样。utf8mb4和utf8的区别是它还能存emoji和一些特殊字符,在项目里直接用utf8mb4是现在的标准做法。

4.2 Swing界面卡死

新手写Swing最容易犯的错就是把耗时操作直接写在事件监听器里,比如点“查询”按钮后直接去执行SQL,数据库慢一点,整个窗口就变白屏、无响应。这是因为Swing的事件分发线程(EDT)被过长的数据库操作阻塞了,鼠标点击、键盘输入等事件都没人处理。

解决办法是使用SwingWorker,把耗时操作放到后台线程执行,执行完毕后再回调到EDT更新界面:

SwingWorker<List<Employee>, Void> worker = new SwingWorker<>() { @Override protected List<Employee> doInBackground() { return employeeDao.findAll(); } @Override protected void done() { try { List<Employee> list = get(); fillTable(list); } catch (Exception e) { JOptionPane.showMessageDialog(MainFrame.this, "查询失败:" + e.getMessage()); } } }; worker.execute();

这个代码看起来复杂,但它的价值很大:查询期间按钮仍然可以点击,用户不会以为程序死机了。课设项目数据量不大,很多同学不写也没事,但如果你的查询涉及多表关联、数据量几千条以上,界面卡顿就会很明显。答辩时主动说“我用了SwingWorker来做异步加载”,又是一个小加分项。

4.3 MySQL连接失败的几个版本

V3测试过程中,我遇到过三类连接失败,问题现象很像但原因完全不同,这里列出来帮你快速定位。

第一种是“Could not create connection to database server”。这个一般是连接串格式有问题或者端口不对,检查URL的前缀、IP、端口、数据库名是否都写对了。常见的坑是写成jdbc:mysql://localhost:3306,但MySQL实际用的不是3306端口,或者数据库名写错了。

第二种是“Client does not support authentication protocol requested by server”。这是MySQL 8.x和旧版连接器版本不匹配导致的。MySQL 8.x默认采用caching_sha2_password加密方式,旧一点的5.x驱动不支持这个协议。解决办法是换用最新版的mysql-connector-java 8.x驱动,或者把MySQL用户改成mysql_native_password认证。

第三种是“Access denied for user 'root'@'localhost'”。这个纯粹是账号密码问题,确认一下DBUtil里填的密码跟MySQL root用户的实际密码是否一致。很多同学装MySQL时随便设了密码,后来忘了,看代码里写的密码是root就以为数据库密码也是root,其实完全不对。

4.4 Java Swing实现下拉框多选功能

不少同学在工资管理模块里遇到了一个需求:要给多个部门或多名员工批量算工资,界面上需要支持“多选”。但Swing自带的JComboBox单选下拉框,天然不支持多选。我在V3里实现了一个轻量方案:用JPopupMenu + JList做自定义多选下拉框。

核心思路是:用一个只读的JTextField当显示框,点击时弹出一个JPopupMenu,里面放一个JList(开启MULTIPLE_INTERVAL_SELECTION模式),选中的选项用逗号拼起来显示在文本框里。关键代码如下:

JTextField field = new JTextField(20); JList<String> list = new JList<>(items); list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); JPopupMenu popup = new JPopupMenu(); popup.add(new JScrollPane(list), BorderLayout.CENTER); field.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { popup.setPreferredSize(new Dimension(field.getWidth(), 150)); popup.show(field, 0, field.getHeight()); } }); list.addListSelectionListener(e -> { ListSelectionModel model = (ListSelectionModel) e.getSource(); if (!model.isSelectionEmpty()) { StringBuilder sb = new StringBuilder(); for (String item : list.getSelectedValuesList()) { sb.append(item).append(", "); } field.setText(sb.substring(0, sb.length() - 2).toString()); } });

这个方案不需要引入第三方库,纯Swing就能搞定,代码量也不大,但能极大提升工资模块的操作体验。如果你想要一个更接近真实产品级别的组件,可以了解下JIDE或swingx库里的多选下拉框,但课设场景下自己实现十几个方法已经足够了,而且还能跟老师讲清楚组件原理,算是意外收获。

5. 课设加分项与答辩应对思路

代码写完了,系统能跑起来了,接下来最重要的事是准备答辩。我见过太多代码写得不错但答辩表现糟糕的案例。下面这几个问题是老师最喜欢问的,提前准备好答案,整个答辩过程会从容很多。

5.1 老师最常追问的四个问题

第一个问题:为什么用DECIMAL不用Double?这个问题在2.1章节已经讲过了,核心是浮点精度损失。回答时要顺带背出一个例子:0.1 + 0.2在二进制浮点下不等于0.3。如果老师追问金额字段精度设置多少合适,回答DECIMAL(10,2)——10位总长度,2位小数,最高可存9千9百多万,对员工工资完全够用。

第二个问题:数据库连接为什么不关?这个问题其实是反向问,考察你对资源管理的理解。如果答“我一直关”,老师会追问“用什么关,关哪些东西”。正确答案是通过DBUtil的close方法统一关闭ResultSet、PreparedStatement、Connection,释放顺序是后打开的先关。还可以补充一句“关闭操作放在finally或try-with-resources里,保证异常情况下也能释放”,这在Java 7之后是标准写法。

第三个问题:怎么防止SQL注入?我之前看到一个同学的代码把所有SQL都用字符串拼接,老师当场让他输入一个特殊参数试试,结果SQL就报错了。正确做法是全部使用PreparedStatement占位符,参数用setObject传入,这是JDBC层防御SQL注入最直接的手段。如果能补充一句“PreparedStatement还能预编译,提高重复查询的性能”,效果更好。

第四个问题:这个系统如果让两人同时操作会有什么问题?这涉及并发。比如两个人同时给同一个员工生成6月工资,如果数据库没有唯一约束,就会插入两条重复记录。因为我们在salary_detail表设计了(emp_id, month)联合唯一键,第二个人插入时会直接报主键冲突,数据库会拦下这条非法数据。这个问题能答上来,说明你真的考虑过生产环境中的数据问题。

5.2 时间允许的情况下还能加的扩展点

如果你的课设周期还有富余,以下几个扩展点按性价比排序,第一个最推荐:

  • 考勤导入功能:用表格一次性粘贴考勤数据,比逐条录入快得多。实现上就是读取Excel或CSV,然后批量插入attendance表。
  • 工资条打印/查看:每个员工登录后只能看到自己的工资明细,这需要在admin_user之外再加一个员工账号表,再开启一个员工登录入口。功能不复杂,但系统完整度一下子就上去了。
  • 图形化统计:用JFreeChart画出各部门平均工资柱状图,导出的报表配上图表,视觉效果很震撼。唯一要注意的是JFreeChart需要额外引入jar包,版本上记得选稳定版。
  • 数据库备份脚本:在系统设置里放一个“一键备份”按钮,代码里调用mysqldump命令导出SQL文件,既实用又体现运维思维。

扩展点做的时候注意不要把原有架构推翻重来,最好是在现有功能上做加法。比如你在员工管理里加一个“批量导入”,改动范围就控制在导入面板和DAO层,不影响其他模块。保持系统稳定性和可演示性,比盲目堆功能更重要。

写在最后:一点个人体会

这套员工工资管理系统V3打磨到最终版本,最让我意外的是,收获最大的反而不是代码本身,而是“怎么把一个问题想完整”这件事。从V1的随意建表,到V3的流水表设计;从所有SQL到处拼字符串,到全部改造成PreparedStatement;从界面卡死无响应,到用SwingWorker解决异步加载——每一次重构,本质都是对“如果真实环境下出问题怎么办”这个问题的默认回答。

如果你正在做类似的课设,我的建议很简单:先不要急着敲代码,花一个晚上把数据库表设计出来,把每个业务模块的数据流转想清楚,再动手写,你会发现后面写代码的速度比盲写快得多。课设不是代码越多越好,而是你的每个设计决策都能讲出道理,这才是课程设计真正想锻炼的能力。

本文还有配套的精品资源,点击获取

http://www.cnnetsun.cn/news/4300130.html

相关文章:

  • 腾讯校招2016编程题解析:格雷码、摩尔投票与动态规划
  • 从零搭建JARVIS语音助手:语音识别+大模型+语音合成全流程
  • B站社招面试全流程复盘:从投递到Offer的备考策略与避坑指南
  • 容器预热预跳转方案
  • 小苯的能量项链【牛客tracker 每日一题】
  • 0.3%差距背后的技术选型真相:从DeepSeek接入Claude Code看工程成本
  • 湿度传感器的类型有哪些?国产平替的优势
  • ROS2机器人自主导航与视觉系统构建实战指南
  • Rmweb:为reMarkable Paper Pro打造的软件渲染墨水屏浏览器
  • 从OpenAI自研芯片看AI芯片之争:GPU、CUDA与开发者实战
  • 【2026年】通风柜气流组织CFD仿真分析与应用
  • 水下图像增强融合算法MATLAB实现与参数调优详解
  • Python 的异常处理机制 —— 可选导入:开源包init.py优雅降级实践
  • 【AI 业务流架构师】04-Markdown调教法:铸造Agent的人格内核与价值观
  • STM32H723ZGT6与AT25SF128A:外部加载器开发与SPI Nor Flash烧录实战
  • 12岁小学生重构Python代码:一场教科书级重构实战
  • 网易运维开发笔试真题复盘:Linux、脚本、监控与CI/CD考点全解析
  • GitHub每日热评|OpenAI Codex 源码解析:一个 Rust 工具型项目是如何组织 CLI、工作流与测试的
  • 国企绩效考核破局之道:从制度设计到数字赋能的完整路径
  • Java SE 基础 · 点1 封装
  • 驱动盘清理SOP:告别仓库爆满,一套流程搞定绝区零装备管理
  • STM32C5 ADC交错采样配置实战:从原理到CubeMX与DMA调试
  • 低功耗MCU踩坑:STANDBY下SideKick协处理器GPIO误判根因与修复
  • 智能体延迟优化指南:从毫秒级推理到工具调用链路
  • SSM停车场管理系统源码解析:从框架原理到部署实战
  • 数据库工程与查询优化案例深度复盘‌
  • 工厂数字孪生平台选型指南:从车间透明化到能源可视化
  • 2013年Google笔试题精讲:从算法内核到面试实战的修炼指南
  • PDF流式编辑实现文字修改自动重排版:原理、实践与工具
  • 雌激素雄性化神经通路的Python模拟:从机制到代码