ARTICLE DETAIL

资讯详情

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

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

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运行的完整工程含数据库建表语句与典型测试数据适合作为课程作业参考、期末项目模板或SwingMySQL技术栈入门实践范例。 很多同学做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 ?useUnicodetruecharacterEncodingUTF-8 useSSLfalseserverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue; 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里那一长串参数每个都不是随便加的。characterEncodingUTF-8解决中文乱码serverTimezoneAsia/Shanghai解决MySQL 8.x版本出现的时区报错useSSLfalse避免连接时SSL握手警告allowPublicKeyRetrievaltrue解决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 {员工管理, 考勤管理, 工资管理, 报表统计, 系统设置}; JListString 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 ListEmployee 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 ); ListObject 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, ListSalaryDetail 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末尾加serverTimezoneAsia/ShanghaiSQL中文字符变成???数据库表字符集或连接字符集不对建表用utf8mb4URL加characterEncodingUTF-8Excel打开CSV文件中文乱码文件没有带BOM头写入前先输出\uFEFF窗口点击按钮后卡死、无响应数据库查询放到了Swing事件线程上执行改用SwingWorker或线程池执行耗时操作工资金额出现0.30000000000000004使用了浮点类型字段所有金额改用DECIMALJava侧用BigDecimal同一个月重复生成工资缺少唯一约束表加UNIQUE KEY (emp_id, month)4.1 中文乱码问题Swing界面中文乱码通常有三个来源。第一个是IntelliJ IDEA控制台乱码这个最隐蔽因为界面里显示正常但输出的日志是乱码排查半天才发现是IDEA自身的编码问题。解决办法是打开Help Edit Custom VM Options加上一行-Dfile.encodingUTF-8然后重启IDEA。第二个来源是MySQL连接串没加编码参数。URL必须以characterEncodingUTF-8结尾否则从数据库读出来的中文字符串就是乱码。注意这里的参数名大小写也不能错我见过写成charSetutf8这种歪写法在部分版本的驱动里是不生效的。第三个来源是建表时字符集选错了。表或字段的字符集默认是latin1的话就算连接串对了也白搭。最稳妥的做法是建库时就指定utf8mb4如上文SQL那样。utf8mb4和utf8的区别是它还能存emoji和一些特殊字符在项目里直接用utf8mb4是现在的标准做法。4.2 Swing界面卡死新手写Swing最容易犯的错就是把耗时操作直接写在事件监听器里比如点“查询”按钮后直接去执行SQL数据库慢一点整个窗口就变白屏、无响应。这是因为Swing的事件分发线程(EDT)被过长的数据库操作阻塞了鼠标点击、键盘输入等事件都没人处理。解决办法是使用SwingWorker把耗时操作放到后台线程执行执行完毕后再回调到EDT更新界面SwingWorkerListEmployee, Void worker new SwingWorker() { Override protected ListEmployee doInBackground() { return employeeDao.findAll(); } Override protected void done() { try { ListEmployee 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 rootlocalhost”。这个纯粹是账号密码问题确认一下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); JListString 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解决异步加载——每一次重构本质都是对“如果真实环境下出问题怎么办”这个问题的默认回答。如果你正在做类似的课设我的建议很简单先不要急着敲代码花一个晚上把数据库表设计出来把每个业务模块的数据流转想清楚再动手写你会发现后面写代码的速度比盲写快得多。课设不是代码越多越好而是你的每个设计决策都能讲出道理这才是课程设计真正想锻炼的能力。本文还有配套的精品资源点击获取
返回列表