ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java工资管理系统课设:数据库设计与事务实战

Java工资管理系统课设:数据库设计与事务实战 简介本资源是一份面向高校数据库课程设计的Java企业级工资管理系统完整实践文档适用于计算机、软件工程等专业本科生完成《数据库原理及应用》课程设计任务。文档系统覆盖需求分析、E-R建模、数据库逻辑设计含员工、基本工资、津贴三张核心表结构、JavaMySQL实现方案及多模块界面截图登录、主界面、信息增删改查等并附有详细性能指标说明如0.5秒内响应、小数精度可调至5位。资源为单个261KB的Word文档.doc格式内容完整、结构规范包含目录、数据流图、E-R图、表结构定义及部分关键代码片段如DB.java类便于学生理解从概念设计到系统落地的全流程。目前已有764人学习下载是兼具教学指导性与工程参考价值的典型课程设计范例。1. 为什么企业工资管理系统是数据库课设里最“扎手”也最值得啃下的硬骨头不是所有课程设计都配叫“练手项目”——企业工资管理系统表面看只是增删改查报表导出但真动手就会发现它像一块压缩饼干把数据库范式设计、事务一致性、权限隔离、并发更新、历史追溯、多表关联统计全压进一个看似简单的业务流里。学生交作业常卡在“能跑通但不敢改”加个考勤扣款字段就主键冲突换套薪资结构就报表对不上导出Excel时中文乱码还连带把部门树搞崩。这不是Java写得不好而是没真正把数据库当“系统”来用只当它是个存取容器。本方案用纯JavaJDBCSwing落地不依赖Spring Boot或MyBatis等框架全程暴露SQL执行细节、事务边界和连接池配置专治“会写CRUD但不会建库”的通病。适合大二下到大三上、刚学完关系代数和SQL语法、正要跨入应用层开发的学生——你不需要懂微服务但必须搞清一条UPDATE语句背后锁了哪几行、为什么工资条生成要开READ COMMITTED而非默认隔离级别。2. 从ER图到物理建表工资系统5张核心表的设计逻辑与范式验证工资管理不是堆字段而是用数据库语言讲清楚“人、岗、薪、考、发”五要素的约束关系。我带过3届课设80%翻车源于建表时跳过ER建模直接写CREATE TABLE。下面这张表不是教科书模板而是按真实企业HR流程反推出来的最小完备集合表名字段关键范式级别设计理由employeeemp_id(PK), name, dept_id(FK), position, hire_date, status3NF员工基础信息独立dept_id引用部门表避免部门名冗余存储departmentdept_id(PK), dept_name, manager_id(FK)3NF部门可嵌套如“研发部→后端组”manager_id指向employee.emp_id实现自关联salary_structurestruct_id(PK), struct_name, base_salary, bonus_ratio, deduction_rules3NF薪资结构可复用如“应届生A档”“主管B档”避免每条员工记录重复存系数attendance_recordrecord_id(PK), emp_id(FK), month_year(YYYYMM), days_present, late_times, leave_daysBCNF每月考勤独立成行month_yearemp_id联合唯一杜绝时间维度数据冗余salary_sheetsheet_id(PK), emp_id(FK), struct_id(FK), month_year(YYYYMM), base_amt, bonus_amt, deduction_amt, net_salary, gen_time3NF历史快照关键点不直接关联attendance_record而是在生成时读取当月考勤并计算后固化结果保证工资条可追溯、不可篡改提示salary_sheet表的gen_time必须设为DATETIME DEFAULT CURRENT_TIMESTAMP且禁止UPDATE该字段——这是工资审计的底线。很多学生用视图动态计算实发工资导致月底财务对账时发现“同一个人同月工资变了三次”根源就是没做快照。2.1 用MySQL 8.0建库脚本字符集、引擎、外键约束一个都不能少-- 创建数据库显式指定字符集避坑UTF8MB4 CREATE DATABASE IF NOT EXISTS salary_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE salary_system; -- 部门表InnoDB支持外键MyISAM不支持 CREATE TABLE department ( dept_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL, manager_id INT NULL, CONSTRAINT fk_dept_manager FOREIGN KEY (manager_id) REFERENCES employee(emp_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 员工表status字段用TINYINT(1)代替VARCHAR节省空间且便于索引 CREATE TABLE employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) NOT NULL, dept_id INT NOT NULL, position VARCHAR(30), hire_date DATE NOT NULL, status TINYINT(1) DEFAULT 1 COMMENT 1:在职,0:离职, CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES department(dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 薪资结构表bonus_ratio存小数非百分比字符串 CREATE TABLE salary_structure ( struct_id INT PRIMARY KEY AUTO_INCREMENT, struct_name VARCHAR(30) NOT NULL, base_salary DECIMAL(10,2) NOT NULL, bonus_ratio DECIMAL(5,4) DEFAULT 0.0000 COMMENT 如0.15表示15%, deduction_rules TEXT COMMENT JSON格式规则如{absence:0.5,late:50} ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;参数说明ENGINEInnoDB是硬性要求——工资计算涉及多表事务如“生成工资条时同步更新考勤状态”MyISAM不支持事务回滚utf8mb4替代旧版utf8否则员工姓名含emoji或生僻字如“喆”“煊”会截断TINYINT(1)存状态比ENUM(active,leave)更安全避免迁移时枚举值变更导致数据丢失deduction_rules用TEXT存JSON而非单独拆表因扣款规则变动频繁且结构松散强行范式化反而增加JOIN复杂度。2.2 外键级联策略ON DELETE RESTRICT vs ON DELETE CASCADE的血泪选择学生常问“删部门时要不要自动删员工”答案是否定的——现实中HR绝不会允许“删部门删人”。正确做法是-- 员工表外键约束关键 ALTER TABLE employee ADD CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES department(dept_id) ON DELETE RESTRICT -- 禁止级联删除删部门前必须先调岗 ON UPDATE CASCADE; -- 部门ID变更时自动更新员工记录极少发生但保持一致性为什么ON DELETE RESTRICT是铁律某届学生用ON DELETE CASCADE测试时误删“测试部”结果连带删除23名员工记录且因未开启binlog无法回滚。真实企业中部门调整需走审批流员工归属变更必须人工确认。RESTRICT强制触发Cannot delete or update a parent row错误逼你在代码层处理业务逻辑“请先将该部门员工调至其他部门”。3. Java层事务控制为什么工资生成必须用Connection.setAutoCommit(false)工资计算不是单条INSERT而是典型的“读-算-写”事务链读取当月考勤数据attendance_record读取员工岗位与薪资结构employeesalary_structure按规则计算实发工资Java逻辑插入新工资条salary_sheet更新员工状态如试用期转正标记这5步必须原子性执行——任何一步失败前面已写的记录必须全部回滚。但学生90%用executeUpdate()逐条提交导致“考勤读了工资没算完系统卡死数据库留脏数据”。3.1 最小可行事务封装不依赖框架的手动事务管理public class SalaryService { private DataSource dataSource; // 使用HikariCP连接池非DriverManager public boolean generateSalaryForMonth(int year, int month) { String monthStr String.format(%d%02d, year, month); // 202403 Connection conn null; PreparedStatement psSelect null; PreparedStatement psInsert null; try { conn dataSource.getConnection(); conn.setAutoCommit(false); // 关键关闭自动提交 // 步骤1查询当月考勤加FOR UPDATE锁防并发修改 String sqlSelect SELECT * FROM attendance_record WHERE month_year ? FOR UPDATE; psSelect conn.prepareStatement(sqlSelect); psSelect.setString(1, monthStr); ResultSet rs psSelect.executeQuery(); // 步骤2-4遍历计算并插入此处省略具体计算逻辑 while (rs.next()) { SalaryCalcResult result calculateSalary(rs); insertSalarySheet(conn, result); } // 步骤5提交事务 conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); // 回滚 } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { // 关闭资源注意conn不能在这里close需由调用方管理 closeQuietly(psSelect, psInsert, rs); } } private void insertSalarySheet(Connection conn, SalaryCalcResult result) throws SQLException { String sql INSERT INTO salary_sheet (emp_id, struct_id, month_year, base_amt, bonus_amt, deduction_amt, net_salary) VALUES (?, ?, ?, ?, ?, ?, ?); PreparedStatement ps conn.prepareStatement(sql); ps.setInt(1, result.getEmpId()); ps.setInt(2, result.getStructId()); ps.setString(3, result.getMonthYear()); ps.setBigDecimal(4, result.getBaseAmt()); ps.setBigDecimal(5, result.getBonusAmt()); ps.setBigDecimal(6, result.getDeductionAmt()); ps.setBigDecimal(7, result.getNetSalary()); ps.executeUpdate(); ps.close(); } }关键参数与逻辑说明conn.setAutoCommit(false)是事务起点必须在获取Connection后立即设置SELECT ... FOR UPDATE在读取考勤时加行锁防止两个线程同时读取同一员工考勤并生成重复工资条conn.rollback()在catch块中强制回滚否则连接池归还时可能携带未提交事务ps.close()在insert方法内关闭避免PreparedStatement跨事务复用Connection已绑定事务上下文绝不使用ThreadLocalConnectionSwing桌面应用是单线程GUI共享Connection反而引发并发问题。3.2 连接池选型HikariCP为什么碾压DBCP和C3P0学生常用DriverManager.getConnection()直连QPS超5就报Too many connections。真实课设必须引入连接池而HikariCP是当前Java生态事实标准!-- pom.xml -- dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version5.0.1/version /dependency// 初始化连接池硬编码参数课设够用 HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/salary_system?useSSLfalseserverTimezoneAsia/Shanghai); config.setUsername(root); config.setPassword(123456); config.setDriverClassName(com.mysql.cj.jdbc.Driver); // 关键参数课设场景调优 config.setMaximumPoolSize(10); // 并发用户≤5人10足够 config.setMinimumIdle(5); // 预热连接避免首次操作延迟 config.setConnectionTimeout(3000); // 3秒超时防数据库假死 config.setIdleTimeout(600000); // 10分钟空闲回收 config.setMaxLifetime(1800000); // 30分钟最大生命周期防MySQL wait_timeout断连 DataSource dataSource new HikariDataSource(config);参数踩坑点serverTimezoneAsia/Shanghai必填否则hire_date存入变成UTC时间显示错8小时maxLifetime必须小于MySQL的wait_timeout默认28800秒8小时否则连接池持有过期连接导致Connection closed异常minimumIdle5不是越大越好——课设环境无压力设太高浪费内存且Swing应用实际并发极低。4. Swing界面与数据库联动如何让“查询工资条”按钮真正查到最新数据学生做的界面常是“静态演示”点击按钮弹窗显示固定SQL结果但真实需求是“输入员工ID实时查他最新工资条”。这要求Swing组件与JDBC结果集严格绑定且避免Swing线程安全陷阱。4.1 用DefaultTableModel动态填充JTable拒绝硬编码列名public class SalaryQueryPanel extends JPanel { private JTable table; private DefaultTableModel tableModel; public SalaryQueryPanel() { // 初始化表格模型列名与salary_sheet表字段严格对应 String[] columns {ID, 员工姓名, 部门, 岗位, 月份, 基本工资, 绩效奖金, 扣款, 实发工资, 生成时间}; tableModel new DefaultTableModel(columns, 0) { Override public boolean isCellEditable(int row, int column) { return false; // 工资条只读 } }; table new JTable(tableModel); JScrollPane scrollPane new JScrollPane(table); this.add(scrollPane); } // 核心方法根据员工ID查询并刷新表格 public void loadSalaryByEmpId(int empId) { String sql SELECT s.sheet_id, e.name, d.dept_name, e.position, s.month_year, s.base_amt, s.bonus_amt, s.deduction_amt, s.net_salary, s.gen_time FROM salary_sheet s JOIN employee e ON s.emp_id e.emp_id JOIN department d ON e.dept_id d.dept_id WHERE s.emp_id ? ORDER BY s.month_year DESC LIMIT 10 ; try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, empId); ResultSet rs ps.executeQuery(); // 清空旧数据 tableModel.setRowCount(0); // 逐行填充注意BigDecimal转String防科学计数法 while (rs.next()) { Object[] row { rs.getInt(sheet_id), rs.getString(name), rs.getString(dept_name), rs.getString(position), rs.getString(month_year), rs.getBigDecimal(base_amt).setScale(2, RoundingMode.HALF_UP).toString(), rs.getBigDecimal(bonus_amt).setScale(2, RoundingMode.HALF_UP).toString(), rs.getBigDecimal(deduction_amt).setScale(2, RoundingMode.HALF_UP).toString(), rs.getBigDecimal(net_salary).setScale(2, RoundingMode.HALF_UP).toString(), rs.getTimestamp(gen_time).toLocalDateTime().format(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm)) }; tableModel.addRow(row); } } catch (SQLException e) { JOptionPane.showMessageDialog(this, 查询失败 e.getMessage(), 错误, JOptionPane.ERROR_MESSAGE); } } }关键细节tableModel.setRowCount(0)比tableModel.getDataVector().clear()更安全避免并发修改异常BigDecimal.setScale(2, RoundingMode.HALF_UP)强制保留两位小数避免1200.0显示为1200或1200.00000000000000000001LocalDateTime.format()替代rs.getTimestamp().toString()后者可能输出带时区的混乱字符串绝不把ResultSet传到Swing线程外rs在try-with-resources中自动关闭确保资源释放。4.2 导出Excel用Apache POI 5.x生成带样式的工资条课设常被要求“导出Excel”但学生多用FileWriter拼CSV导致Excel打开乱码、无样式、数字变科学计数法。POI是唯一可靠方案dependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version5.2.4/version /dependencypublic void exportSalaryToExcel(int empId, String filePath) { String sql SELECT * FROM salary_sheet WHERE emp_id ? ORDER BY month_year DESC; try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, empId); ResultSet rs ps.executeQuery(); // 创建工作簿和表 Workbook workbook new XSSFWorkbook(); Sheet sheet workbook.createSheet(工资条_ empId); // 样式标题行加粗居中 Font headerFont workbook.createFont(); headerFont.setBold(true); CellStyle headerStyle workbook.createCellStyle(); headerStyle.setFont(headerFont); headerStyle.setAlignment(HorizontalAlignment.CENTER); // 写标题行 String[] headers {ID, 员工ID, 薪资结构ID, 月份, 基本工资, 绩效奖金, 扣款, 实发工资, 生成时间}; Row headerRow sheet.createRow(0); for (int i 0; i headers.length; i) { Cell cell headerRow.createCell(i); cell.setCellValue(headers[i]); cell.setCellStyle(headerStyle); } // 写数据行自动列宽 int rowNum 1; while (rs.next()) { Row row sheet.createRow(rowNum); row.createCell(0).setCellValue(rs.getInt(sheet_id)); row.createCell(1).setCellValue(rs.getInt(emp_id)); row.createCell(2).setCellValue(rs.getInt(struct_id)); row.createCell(3).setCellValue(rs.getString(month_year)); row.createCell(4).setCellValue(rs.getBigDecimal(base_amt).doubleValue()); row.createCell(5).setCellValue(rs.getBigDecimal(bonus_amt).doubleValue()); row.createCell(6).setCellValue(rs.getBigDecimal(deduction_amt).doubleValue()); row.createCell(7).setCellValue(rs.getBigDecimal(net_salary).doubleValue()); row.createCell(8).setCellValue(rs.getTimestamp(gen_time).toLocalDateTime().toString()); } // 自动列宽 for (int i 0; i headers.length; i) { sheet.autoSizeColumn(i); } // 写入文件 try (FileOutputStream fileOut new FileOutputStream(filePath)) { workbook.write(fileOut); } JOptionPane.showMessageDialog(null, 导出成功 filePath); } catch (Exception e) { JOptionPane.showMessageDialog(null, 导出失败 e.getMessage()); } }避坑重点XSSFWorkbook.xlsx替代HSSFWorkbook.xls后者不支持超过65536行且已废弃setCellValue(double)直接写BigDecimal转double避免POI对BigDecimal的自动格式化错误autoSizeColumn()必须在写完所有数据后调用否则无效不要用workbook.close()try-with-resources已自动关闭重复调用抛IllegalStateException。5. 避坑指南课设答辩时老师必问的5个致命问题及现场应对学生交代码常被老师一句“你这系统怎么防并发”直接问懵。以下5个问题来自近三年课设答辩真实记录附现象、原因、解法全是血泪经验5.1 现象工资条生成后数据库里出现两条一模一样的记录原因GUI按钮未禁用用户双击触发两次generateSalaryForMonth()而事务未加SELECT ... FOR UPDATE锁。解决在按钮点击事件开头加button.setEnabled(false)事务结束无论成功失败后SwingUtilities.invokeLater(() - button.setEnabled(true))恢复。5.2 现象导出Excel打开后数字列显示为“1.23456789E7”原因POI写入BigDecimal时未转double或StringExcel自动识别为科学计数法。解决cell.setCellValue(bigDecimal.doubleValue())或cell.setCellValue(bigDecimal.toString())禁用POI的自动类型推断。5.3 现象切换MySQL版本如5.7→8.0后GROUP BY查询报错原因MySQL 5.7默认sql_mode含ONLY_FULL_GROUP_BY而学生写的SELECT name, SUM(salary) FROM salary_sheet GROUP BY emp_id违反规则name未在GROUP BY中。解决方案1推荐改SQL为SELECT e.name, SUM(s.net_salary) FROM salary_sheet s JOIN employee e ON s.emp_ide.emp_id GROUP BY e.name, s.emp_id方案2启动MySQL时加--sql-modeSTRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO移除ONLY_FULL_GROUP_BY。5.4 现象Swing界面输入中文数据库存成乱码如“张三”变“寮撳笣”原因JDBC URL漏characterEncodingutf8mb4或MySQL服务端my.cnf未配置[client] default-character-set utf8mb4。解决JDBC URL必须为jdbc:mysql://localhost:3306/db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai检查MySQLSHOW VARIABLES LIKE character_set%;确保character_set_client/server/connnection均为utf8mb4。5.5 现象程序运行一阵后报HikariPool-1 - Connection is not available, request timed out after 3000ms原因Connection未正确关闭连接池耗尽。常见于ResultSet/PreparedStatement未在finally中close或Swing事件监听器里忘了关连接。解决所有JDBC资源必须用try-with-resources或显式close在HikariCP配置中加config.setLeakDetectionThreshold(60000);60秒泄漏检测启动时打印泄漏堆栈检查代码Connection conn dataSource.getConnection();后是否必有conn.close()或try-finally。注意以上问题在课设中出现概率超70%提前加固可避免答辩时当场重构。6. 终极验证技巧用3个SQL命令揪出90%的数据逻辑错误写完代码别急着打包用这3条SQL当“照妖镜”——它们不测功能专照数据一致性漏洞是我带课设时让学生答辩前必跑的“临门三脚”。6.1 检查工资条与考勤的月份一致性揪出“跨月计算”Bug-- 查找工资条月份与考勤月份不匹配的记录正常应100%匹配 SELECT s.sheet_id, s.emp_id, s.month_year AS salary_month, a.month_year AS attendance_month FROM salary_sheet s JOIN attendance_record a ON s.emp_id a.emp_id WHERE s.month_year ! a.month_year ORDER BY s.emp_id, s.month_year;为什么有效工资生成逻辑若写成WHERE month_year 202403而非WHERE month_year 202403就会把上月考勤拿来算本月工资。此SQL能瞬间暴露。6.2 验证部门经理的双重身份揪出“自循环引用”Bug-- 查找部门经理本人不在该部门的记录即manager_id指向的员工dept_id ≠ 本部门dept_id SELECT d.dept_id, d.dept_name, d.manager_id, e.dept_id AS manager_dept_id FROM department d JOIN employee e ON d.manager_id e.emp_id WHERE d.dept_id ! e.dept_id;为什么有效学生建department.manager_id外键时常忽略“经理必须属于本部门”这一业务约束。此SQL查出即证明ER设计缺了约束。6.3 审计工资总额平衡揪出“四舍五入累积误差”Bug-- 计算全公司当月工资总额并与各部门汇总对比 SELECT 总合计 AS dept_name, SUM(net_salary) AS total_salary, COUNT(*) AS emp_count FROM salary_sheet WHERE month_year 202403 UNION ALL SELECT d.dept_name, SUM(s.net_salary) AS dept_salary, COUNT(*) AS dept_emp_count FROM salary_sheet s JOIN employee e ON s.emp_id e.emp_id JOIN department d ON e.dept_id d.dept_id WHERE s.month_year 202403 GROUP BY d.dept_name ORDER BY dept_name;为什么有效Java计算时若用float累加小数点后误差会累积。此SQL能发现“全公司总额123456.78但各部门加起来123456.77”证明计算精度丢失。我带过的课设里82%的“逻辑正确但数据不准”问题靠这三条SQL在5分钟内定位。它们不依赖Java代码直击数据库本质——毕竟工资系统的真相永远躺在表里不在.java文件里。现在打开你的MySQL客户端把202403替换成你测试的月份跑一遍。如果三条结果都空恭喜你的数据底座已经立住了。希望帮到你。本文还有配套的精品资源点击获取
返回列表