ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

JavaSwing+MySQL企业员工培训管理系统课程设计实战:从建库到绩效评估

2026/10/6 12:47:42 拓冰建站 浏览量
JavaSwing+MySQL企业员工培训管理系统课程设计实战:从建库到绩效评估 简介这份资源是湖南科技大学数据库系统课程设计的完整项目包面向高校计算机相关专业学生及需要Java桌面应用实战练习的开发者围绕企业员工培训管理场景解决培训计划编排、课程考勤记录、培训资源分配与员工绩效评估等业务问题。压缩包共67个文件约4.06MB以49个Java源码为核心辅以SQL初始数据脚本、Gradle构建文件、properties配置、drawio的E-R图以及课程设计报告、题目要求、指导书等PDF文档和说明文件结构完整、层次清晰。已有45人学习下载。读者可据此获得一套可直接运行的JavaSwing加MySQL数据库应用范例涵盖界面搭建、数据库设计与业务逻辑实现并借助配套报告与E-R图理解设计思路适合作为课程设计参考、项目复现或二次开发的基础模板。1. 从一张课程设计选题表说起企业员工培训管理系统到底要解决什么每年到了数据库系统课程设计选题那几周总有一批同学盯着选题表发愁。企业员工培训管理系统这个题目看起来平平无奇但真动手做的时候你会发现它几乎把关系型数据库里最核心的几类操作全串起来了员工信息要管、培训计划要排、课程考勤要记、培训资源要分配、绩效评估要算最后还得把培训结果和员工能力挂钩。用 JavaSwing 做界面、MySQL 做存储这套组合虽然老但胜在稳定、资料多、答辩时老师也认。这篇文章不讲空泛的数据库系统概论而是把我在带课程设计时反复验证过的一套落地路径拆开从建库建表、Swing 界面分层、考勤与资源分配的冲突处理到绩效评估的 SQL 聚合写法再到那些一跑就翻车的配置坑。适合正在做数据库课程设计、想拿一个能跑能讲能扩展的完整项目的同学也适合需要快速搭一个内部培训管理原型的开发者。2. 建库先建脑子员工培训管理系统的表结构怎么定才不返工2.1 先画业务闭环再落五张核心表很多同学一上来就打开 Navicat 建表结果做到考勤模块发现培训计划和课程对不上回头改表结构改到崩溃。我的习惯是先在纸上画一遍业务闭环一个培训计划包含多门课程一门课程对应多个员工报名每个员工每门课有考勤记录课程消耗培训资源场地、设备、讲师培训结束后产生绩效评估。这个闭环决定了五张核心表employee、training_plan、course、attendance、performance再加一张resource表管资源分配。表结构设计时最容易踩的坑是主键和外键的取舍。课程设计里我一般建议用自增整数做主键业务编号如工号、课程编码做唯一索引。这样 Swing 端绑定数据时不用处理复合主键JDBC 取getGeneratedKeys也简单。下面是我常用的建库脚本片段字符集统一用utf8mb4避免员工姓名里有生僻字时出现乱码。CREATE DATABASE IF NOT EXISTS training_mgmt DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE training_mgmt; CREATE TABLE employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT 工号, emp_name VARCHAR(50) NOT NULL, dept VARCHAR(50), position VARCHAR(50), hire_date DATE, status TINYINT DEFAULT 1 COMMENT 1在职 0离职 ) ENGINEInnoDB; CREATE TABLE training_plan ( plan_id INT PRIMARY KEY AUTO_INCREMENT, plan_name VARCHAR(100) NOT NULL, start_date DATE NOT NULL, end_date DATE NOT NULL, budget DECIMAL(10,2) DEFAULT 0, status TINYINT DEFAULT 0 COMMENT 0草稿 1进行中 2已结束 ) ENGINEInnoDB;逻辑说明employee表用emp_no做唯一约束而不是主键是为了让工号可以修改而不影响外键关联。training_plan的status字段用 tinyint 而不是 enum是因为 MySQL 的 enum 在后续加值时需要改表结构课程设计答辩时老师如果让你加一个“已取消”状态tinyint 改起来更从容。参数上budget用DECIMAL(10,2)而不是FLOAT避免绩效评估里金额累加出现浮点误差。2.2 考勤表和资源分配表的字段设计细节考勤表attendance是整套系统里写入最频繁的表设计时要考虑签到方式。课程设计里常见做法是只记“出勤/缺勤/迟到”三态但如果你想做得细一点可以加checkin_time和checkout_time两个 datetime 字段。这里有个血泪经验不要用TIMESTAMP存考勤时间因为TIMESTAMP的范围只到 2038 年而且受时区影响课程设计演示时如果服务器时区没设对签到时间会莫名其妙差 8 小时。用DATETIME最稳。CREATE TABLE attendance ( att_id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, course_id INT NOT NULL, att_date DATE NOT NULL, status TINYINT DEFAULT 1 COMMENT 1出勤 2迟到 3缺勤 4请假, checkin_time DATETIME, remark VARCHAR(200), UNIQUE KEY uk_emp_course_date (emp_id, course_id, att_date), FOREIGN KEY (emp_id) REFERENCES employee(emp_id) ) ENGINEInnoDB;UNIQUE KEY uk_emp_course_date这个联合唯一索引是关键它从数据库层面防止同一个员工同一门课同一天被重复签到。Swing 端做签到按钮时直接捕获SQLIntegrityConstraintViolationException就能提示“今日已签到”不用先查再插减少一次数据库往返。资源分配表resource的设计要区分“资源本身”和“分配记录”。我一般拆成resource资源台账和resource_alloc分配记录两张表。资源台账存场地、设备、讲师的基本信息分配记录存某次培训计划占用了哪个资源、占用时间段。这样查冲突时只需要在resource_alloc上按resource_id和时间段做重叠判断SQL 写起来清晰。提示建表时把所有外键约束都加上ON DELETE RESTRICT课程设计里不要用级联删除。答辩时老师问“删除一个员工他的考勤记录怎么办”你可以回答“先做离职标记保留历史考勤用于绩效追溯”这比级联删除更符合企业实际。3. JavaSwing 连 MySQL从 JDBC 工具类到界面数据绑定的最小闭环3.1 一个能复用的 JDBC 工具类与连接池取舍Swing 是单线程事件模型所有数据库操作如果直接写在按钮的actionPerformed里界面会卡死。课程设计里最稳妥的做法是把 JDBC 操作封装成工具类再配合SwingWorker做后台查询。连接管理上很多教程直接DriverManager.getConnection每次新建连接演示时看不出问题但考勤模块批量导入 200 条记录时就会明显变慢。我的建议是课程设计阶段用简单的单例连接池或者直接引入HikariCP的 jar 包配置 5 个连接就够。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/training_mgmt?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PWD your_password; private static HikariDataSource ds; static { HikariConfig cfg new HikariConfig(); cfg.setJdbcUrl(URL); cfg.setUsername(USER); cfg.setPassword(PWD); cfg.setMaximumPoolSize(5); cfg.setMinimumIdle(1); cfg.setConnectionTimeout(3000); ds new HikariDataSource(cfg); } public static Connection getConn() throws SQLException { return ds.getConnection(); } public static void close(AutoCloseable... res) { for (AutoCloseable r : res) { if (r ! null) try { r.close(); } catch (Exception ignored) {} } } }逻辑说明serverTimezoneAsia/Shanghai这个参数在 MySQL 8.0 之后必须显式指定否则连接时会报The server time zone value ?D1ú±ê×?ê±?? is unrecognized。useSSLfalse在本地开发时关掉 SSL 可以避免证书警告但生产环境要开。maximumPoolSize5对课程设计足够因为 Swing 端并发不高连接太多反而浪费。参数上connectionTimeout3000表示 3 秒拿不到连接就抛异常避免界面无限等待。3.2 Swing 表格模型与分页查询的配合员工列表、培训计划列表这些界面新手最容易写成一次性SELECT *然后全部塞进JTable。数据量小的时候没问题但考勤记录一多界面加载要好几秒。正确做法是实现一个AbstractTableModel子类配合LIMIT和OFFSET做分页。下面是一个简化的分页模型核心代码。public class EmployeeTableModel extends AbstractTableModel { private final String[] cols {工号, 姓名, 部门, 岗位, 入职日期, 状态}; private ListObject[] rows new ArrayList(); private int pageSize 20; private int currentPage 1; Override public int getRowCount() { return rows.size(); } Override public int getColumnCount() { return cols.length; } Override public String getColumnName(int c) { return cols[c]; } Override public Object getValueAt(int r, int c) { return rows.get(r)[c]; } public void loadPage(int page) { this.currentPage page; rows.clear(); String sql SELECT emp_no, emp_name, dept, position, hire_date, status FROM employee ORDER BY emp_id LIMIT ? OFFSET ?; try (Connection conn DBUtil.getConn(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, pageSize); ps.setInt(2, (page - 1) * pageSize); ResultSet rs ps.executeQuery(); while (rs.next()) { rows.add(new Object[]{ rs.getString(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getDate(5), rs.getInt(6) 1 ? 在职 : 离职 }); } } catch (SQLException e) { e.printStackTrace(); } fireTableDataChanged(); } }逻辑说明fireTableDataChanged()通知 JTable 重新渲染这是 Swing 线程安全的做法。分页参数pageSize设 20 是经验值太小翻页频繁太大失去分页意义。OFFSET在数据量大时性能会下降但课程设计的数据规模完全够用。注意ResultSet和PreparedStatement都放在 try-with-resources 里避免连接泄漏。注意Swing 的所有界面更新必须在 Event Dispatch Thread 上执行。如果你在SwingWorker的doInBackground里直接调fireTableDataChanged()会抛IllegalStateException。正确做法是在done()方法里更新模型。4. 考勤记录与资源分配两个最容易出并发问题的模块怎么落地4.1 考勤批量签到的 SQL 写法与事务边界考勤模块的典型场景是培训当天管理员打开签到界面勾选一批员工点“批量签到”。如果逐条INSERT200 个人就要 200 次数据库往返。更好的做法是用INSERT ... ON DUPLICATE KEY UPDATE或者批量addBatch。我一般用addBatch配合事务因为逻辑更直观也方便统计成功和失败条数。public int[] batchCheckin(ListInteger empIds, int courseId, Date attDate) { String sql INSERT INTO attendance (emp_id, course_id, att_date, status, checkin_time) VALUES (?, ?, ?, 1, NOW()) ON DUPLICATE KEY UPDATE status 1, checkin_time NOW(); int[] result new int[0]; try (Connection conn DBUtil.getConn()) { conn.setAutoCommit(false); try (PreparedStatement ps conn.prepareStatement(sql)) { for (Integer empId : empIds) { ps.setInt(1, empId); ps.setInt(2, courseId); ps.setDate(3, attDate); ps.addBatch(); } result ps.executeBatch(); conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } } catch (SQLException e) { e.printStackTrace(); } return result; }逻辑说明ON DUPLICATE KEY UPDATE依赖前面建的联合唯一索引uk_emp_course_date如果员工已经签到过就更新签到时间而不是报错。事务边界设在conn上executeBatch返回的数组里正数表示插入或更新成功Statement.SUCCESS_NO_INFO表示成功但无具体行数。参数上attDate用java.sql.Date而不是java.util.Date避免 JDBC 驱动转换时丢时间部分。4.2 资源分配冲突检测一条 SQL 判断时间段重叠资源分配最怕的是同一个场地在同一时间段被两个培训计划占用。判断时间段重叠的经典 SQL 条件是existing.start new.end AND existing.end new.start。把这个条件写进查询就能在分配前做冲突检测。SELECT r.resource_name, ra.start_time, ra.end_time FROM resource_alloc ra JOIN resource r ON r.resource_id ra.resource_id WHERE ra.resource_id ? AND ra.start_time ? AND ra.end_time ?;逻辑说明三个占位符分别是资源 ID、新分配的结束时间、新分配的开始时间。如果查询返回任何行说明有冲突。这个条件覆盖了四种重叠情况完全包含、部分重叠、被包含、完全相同。参数上时间用DATETIME传入注意 Java 端用Timestamp而不是String避免格式解析问题。提示资源分配表上要建(resource_id, start_time, end_time)的联合索引否则冲突检测在数据量大时会全表扫描。课程设计里数据少可能感觉不到但答辩时老师如果问“怎么优化”这就是一个加分点。5. 绩效评估的 SQL 聚合与 Swing 报表展示把培训结果算清楚5.1 用一条聚合 SQL 算出员工培训绩效分绩效评估模块的核心是把考勤、课程成绩、培训时长加权算出一个总分。很多同学写多条 SQL 然后在 Java 里循环累加代码又长又容易错。其实用一条带CASE WHEN和GROUP BY的 SQL 就能搞定。SELECT e.emp_no, e.emp_name, COUNT(a.att_id) AS total_sessions, SUM(CASE WHEN a.status 1 THEN 1 ELSE 0 END) AS present_count, SUM(CASE WHEN a.status 2 THEN 0.5 ELSE 0 END) AS late_score, ROUND( SUM(CASE WHEN a.status 1 THEN 1 WHEN a.status 2 THEN 0.5 ELSE 0 END) / COUNT(a.att_id) * 100, 2 ) AS attendance_rate FROM employee e LEFT JOIN attendance a ON e.emp_id a.emp_id WHERE e.status 1 GROUP BY e.emp_id, e.emp_no, e.emp_name ORDER BY attendance_rate DESC;逻辑说明LEFT JOIN保证没有考勤记录的员工也会出现在结果里attendance_rate为 NULL 或 0。CASE WHEN把出勤算 1 分、迟到算 0.5 分、缺勤和请假算 0 分再除以总场次得到出勤率。参数上ROUND(..., 2)保留两位小数避免 Swing 表格里出现一长串浮点数。如果绩效还要结合考试成绩可以再 JOIN 一张exam_score表用加权公式计算。5.2 Swing 端用 JFreeChart 展示绩效分布课程设计答辩时纯表格的展示效果一般加一个柱状图或饼图会直观很多。JFreeChart 是 JavaSwing 生态里最常用的图表库和JTable配合很简单。下面是把绩效查询结果转成柱状图数据集的代码片段。public JFreeChart buildPerformanceChart(ListObject[] data) { DefaultCategoryDataset dataset new DefaultCategoryDataset(); for (Object[] row : data) { String name (String) row[1]; Double rate row[3] null ? 0.0 : ((Number) row[3]).doubleValue(); dataset.addValue(rate, 出勤率, name); } return ChartFactory.createBarChart( 员工培训出勤率, 员工, 百分比, dataset, PlotOrientation.VERTICAL, true, true, false); }逻辑说明DefaultCategoryDataset的addValue第一个参数是数值第二个是系列名第三个是分类名。ChartFactory.createBarChart的参数依次是标题、X 轴标签、Y 轴标签、数据集、方向、是否显示图例、是否显示工具提示、是否生成 URL。参数上rate要做空值判断否则NullPointerException会让整个界面崩掉。注意JFreeChart 的 jar 包要加到项目 classpath 里课程设计如果要求“纯 JDK 实现”可以用JPanel的paintComponent手动画柱状图但工作量会大很多。我的建议是提前问清楚老师是否允许第三方库。6. 那些年课程设计里踩过的坑从 MySQL 安装到 Swing 线程的排查清单6.1 MySQL 8.0 连接报错与时区问题现象Swing 程序启动后点任何查询按钮都弹SQLException: The server time zone value ... is unrecognized。原因MySQL 8.0 的 JDBC 驱动要求显式指定时区而很多同学照着旧教程写连接串没加serverTimezone。解决在 JDBC URL 里加serverTimezoneAsia/Shanghai或者执行SET GLOBAL time_zone 8:00。如果还报 SSL 警告加useSSLfalse。6.2 中文乱码从建库到 Swing 的全链路排查现象员工姓名在数据库里查出来是问号或者 Swing 表格里显示方块。原因乱码可能出现在三个环节——建库时字符集不是utf8mb4、JDBC 连接串没加characterEncodingutf8、Swing 字体不支持中文。解决建库时指定DEFAULT CHARACTER SET utf8mb4连接串加characterEncodingutf8Swing 的JLabel和JTable设置字体为Microsoft YaHei或SimSun。6.3 Swing 界面卡死数据库操作放错线程现象点击“查询”按钮后界面冻结几秒按钮点不动。原因数据库查询写在actionPerformed里占用了 Event Dispatch Thread。解决用SwingWorker把查询放到后台线程done()方法里更新表格模型。如果查询很快至少也要用SwingUtilities.invokeLater包一下界面更新。6.4 考勤重复签到导致主键冲突现象批量签到时抛Duplicate entry异常整个批次回滚。原因attendance表有联合唯一索引但代码里用的是普通INSERT。解决改用INSERT ... ON DUPLICATE KEY UPDATE或者在 Java 端先查后插。前者更高效后者逻辑更清晰课程设计里两种都可以但要在答辩时能说清楚区别。6.5 资源分配时间重叠但检测不出来现象两个培训计划占用了同一场地同一时间段系统没有提示冲突。原因冲突检测 SQL 的条件写反了或者时间字段用了DATE而不是DATETIME导致同一天不同时段被误判为不冲突。解决确认 SQL 条件是existing.start new.end AND existing.end new.start时间字段用DATETIMEJava 端传Timestamp。7. 让课程设计多拿十分的一个技巧把绩效评估做成可配置权重绩效评估如果只是固定公式答辩时老师很容易问“如果企业想调整出勤和考试的权重怎么办”。这时候如果你能展示一个可配置的权重表印象分会高很多。做法是在数据库里加一张eval_config表存attendance_weight、exam_weight、practice_weight三个字段Swing 端加一个设置界面绩效计算时从表里读权重而不是写死在 SQL 里。CREATE TABLE eval_config ( config_id INT PRIMARY KEY AUTO_INCREMENT, attendance_weight DECIMAL(3,2) DEFAULT 0.40, exam_weight DECIMAL(3,2) DEFAULT 0.40, practice_weight DECIMAL(3,2) DEFAULT 0.20, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );计算时用attendance_rate * attendance_weight exam_score * exam_weight practice_score * practice_weight。这样老师让你现场改权重你只需要在界面上改个数字不用改代码重新编译。这个技巧我自己做课程设计时用过答辩老师当场让改成出勤 60%、考试 30%、实践 10%系统秒出结果效果很好。另外一个小习惯每次改完数据库结构我都会把建库脚本和测试数据脚本分开存成两个.sql文件一个schema.sql一个data.sql。这样换电脑演示时先跑 schema 再跑 data五分钟就能恢复环境。课程设计最怕的就是答辩前一天数据库崩了有这两个文件就有后悔药。希望帮到你。本文还有配套的精品资源点击获取