资讯动态

数据库课程设计:Java+MySQL企业工资管理系统完整实现与避坑指南

发布时间:2026/10/13 22:20:29 来源:尧图企业网站定制
简介一份基于Java与MySQL的企业工资管理系统课程设计报告面向需要完成数据库原理及应用课程设计的在校生也适合作为期末作业或毕业设计的参考模板。文档包含完整的需求分析与性能要求明确员工信息、基本工资、津贴等核心功能模块并通过第一层数据流图、E-R图梳理数据关系总体设计与详细设计部分给出员工表、基本工资表、津贴表的逻辑结构和主键设计按职工信息管理、职工工资管理、职工登录查询三个模块介绍各自职能。系统实现环节配有界面截图及DB.java等核心代码可帮助理解登录验证、工资汇总、信息增删改查的具体实现思路。压缩包内共有1个Word文档整体约261KB轻量便于下载。当前已有766人学习适合正在撰写同类课设报告并需要文档框架、表结构设计、Java实现参考的读者。1. 数据库课程设计与企业工资管理系统这门课设为什么值得认真做完每年到数据库课程设计节点总有人问我“工资管理系统是不是太简单了换个难的题目行不行”。我的回答通常是企业工资管理系统恰好是数据库课设里最值得做透的一类题目它不靠花哨功能取胜而是把数据库设计的硬功夫全占了——多表关联、事务一致性、权限隔离、按时间维度的数据回溯。Java 版 完整代码这个组合意味着你不只是交一份 ER 图和建表 SQL 上去而是要交付一个能连上 MySQL、能跑通增删改查、能在并发访问下不丢数据的可运行系统。这篇笔记面向三类人正在选课设题目、想找可靠方案直接复现的高校学生刚转 Java 开发、想补一个完整数据库项目经验的初级工程师以及需要给团队搭一个内部工资管理原型、后面还要接 Spring Boot 或微服务改造的后端开发者。我会把设计思路、表结构、核心代码、参数设置和踩坑记录全部拆开讲你照着做就能跑通改一改就能当毕设或项目经验。顺带说一句这个题目在面试里也常被拿来当切入点面试官问“你怎么设计工资表”本质是考你如何建模“金额随时间变化”这类业务。下面从设计开始。2. 系统设计与数据库建模字段、类型和 ER 关系一次定清楚2.1 技术选型为什么是 JavaSE JDBC MySQL而不是直接上 Spring Boot课设场景下的技术选型首先要满足一个原则课程要求什么就围绕什么展开。多数数据库课设的验收标准是“有完整的数据建模、有合理的 SQL 设计、有能跑的程序”而不是“你用到了多新的框架”。所以常见的可靠做法是 JavaSE JDBC MySQL图形界面用 Swing 或 JavaFX连接池用 HikariCP 或 Druid。不直接上 Spring Boot 有两个原因一是课设文档里通常要求体现“数据库设计”而不是“框架配置”你用 Spring Boot 写 DAO 层底层还是 JDBC但多出来的自动配置反而让老师不好判断你对数据库原理掌握了多少二是 JavaSE 项目结构简单交给老师时只需要一个lib目录放驱动包和连接池 jar双击启动类就能跑省去一堆环境问题。如果你已经熟练 Spring Boot可以在完成课设后自己改造但我建议课设本身用最朴素的方式做完。连接池我一般选 HikariCP理由只有一个它对 JDBC 4 的支持好参数少性能在同类里靠前。Druid 也行但它的监控页面在这个项目里用不上徒增配置量。MySQL 版本用 8.0驱动用mysql-connector-j8.0.x注意旧版com.mysql.jdbc.Driver在 8.0 里已经改名成com.mysql.cj.jdbc.Driver这是最常见的翻车点。2.2 表结构设计员工表、部门表、工资表、用户表的字段与约束工资管理系统的核心业务链是部门下有员工员工每个月有一条工资记录工资记录由基本工资、岗位工资、津贴、社保扣除、个税、实发合计组成。用户表单独拎出来因为管理员和普通员工看到的数据范围不同。员工表employee设计如下主键emp_id用自增整数姓名emp_name用VARCHAR(50)部门外键dept_id指向部门表入职日期hire_date用DATE状态status用TINYINT1 表示在职0 表示离职。这里有个细节离职员工的历史工资记录要保留所以不能物理删除员工只能改状态字段。工资表salary是重中之重。不建议把工资字段直接挂在员工表上因为工资是“按月变化”的挂在员工表上就只能保留最新一条。正确做法是单独建表用emp_id关联员工再加一个salary_month字段标识所属月份格式用CHAR(6)存2025-01这样的值。金额字段全部用DECIMAL(10,2)绝对不能用FLOAT或DOUBLE——你去查一下银行系统为什么不用浮点存金额就明白了二进制浮点数在加减乘除时会产生不可预料的舍入误差工资差一分钱都是事故。部门表department就三个字段dept_id、dept_name、dept_manager。注意dept_manager也可以设计成指向员工表的外键但需要先建部门表再建员工表为了省事可以存负责人名字字符串但严谨做法是存emp_id并加外键约束。用户表sys_user包含user_id、username、password、role四个字段。密码不要明文存用 MD5 或 SHA-256 加盐后存储role用VARCHAR(20)区分admin和employee。2.3 外键、索引和存储引擎的边界哪些约束必须有哪些是给自己找麻烦外键要不要用是课设里经常被问到的点。我的建议是主外键关系要在建表 SQL 里体现出来因为老师验收时会看 ER 图和建表语句的一致性但程序代码里不要依赖数据库外键来做级联删除——比如员工被工资表引用时如果误删员工外键约束会直接报错这对用户很不友好。正确做法是删除员工前先检查工资表是否有记录有就改状态为离职没有才允许物理删除。索引方面工资表的salary_month字段一定要建索引因为最常见的查询是“查某个月所有人的工资”。emp_id和salary_month的联合索引也可以建但要注意联合索引的字段顺序emp_id放前面、salary_month放后面这样“查某员工的所有历史工资”和“查某员工某个月工资”都能命中索引。部门表数据量小不需要额外索引。存储引擎选 InnoDB这是 MySQL 的默认引擎。InnoDB 支持事务和行级锁这是工资系统最需要的特性——批量导入工资、计算实发工资时必须保证要么全部成功要么全部回滚。ENGINEInnoDB DEFAULT CHARSETutf8mb4是建表时的标准结尾utf8mb4别写成utf8后者存不了 emoji 和生僻字虽然工资系统里不太会出现 emoji但这是个好习惯。3. Java 代码实现连接池、DAO 层和增删改查的落地写法3.1 编写 DBUtil 工具类HikariCP 参数配置与连接管理DBUtil 是整个项目的基础设施它负责读取配置文件并创建连接池。HikariCP 的配置建议放在db.properties文件里用 Java 的Properties类读取并手动构造HikariConfig。import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class DBUtil { private static HikariDataSource dataSource; static { try { Properties props new Properties(); // 从 classpath 读配置文件这样打包后也能找到 InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties); props.load(in); HikariConfig config new HikariConfig(); config.setJdbcUrl(props.getProperty(jdbcUrl)); config.setUsername(props.getProperty(username)); config.setPassword(props.getProperty(password)); config.setDriverClassName(props.getProperty(driverClassName)); config.setMaximumPoolSize(10); config.setMinimumIdle(5); config.setConnectionTimeout(30000); config.setIdleTimeout(600000); config.setMaxLifetime(1800000); // 从连接池拿连接时自动测试可用性 config.setConnectionTestQuery(SELECT 1); dataSource new HikariDataSource(config); } catch (Exception e) { throw new ExceptionInInitializerError(数据库初始化失败: e.getMessage()); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, java.sql.Statement stmt, java.sql.ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (stmt ! null) { try { stmt.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }对应db.properties文件内容# MySQL 8.0 的连接串必须带 serverTimezone否则报时区错误 jdbcUrljdbc:mysql://localhost:3306/salary_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4 usernameroot password123456 driverClassNamecom.mysql.cj.jdbc.Driver说几个参数含义。maximumPoolSize10表示连接池最多持有 10 个物理连接对于课设这种单机应用完全够用idleTimeout600000表示空闲 10 分钟的连接会被回收防止 MySQL 的wait_timeout默认值把连接掐断maxLifetime1800000是 30 分钟HikariCP 建议这个值要小于数据库的wait_timeout否则会拿到已失效的连接。setConnectionTestQuery(SELECT 1)是保底策略每次发连接前先探活。3.2 员工与部门管理模块PreparedStatement 防 SQL 注入的正确姿势增删改查是课设的硬指标但怎么写有讲究。我见过太多课设代码用Statement拼字符串比如SELECT * FROM employee WHERE emp_name name 这在验收时能跑但面试时一问“怎么防 SQL 注入”就露馅了。正确做法是全程用PreparedStatement参数用?占位。员工新增的代码如下public boolean addEmployee(Employee emp) { String sql INSERT INTO employee (emp_name, dept_id, hire_date, status) VALUES (?, ?, ?, ?); Connection conn null; PreparedStatement pstmt null; try { conn DBUtil.getConnection(); pstmt conn.prepareStatement(sql); pstmt.setString(1, emp.getEmpName()); pstmt.setInt(2, emp.getDeptId()); // 使用 java.sql.Date 来对应数据库 DATE 类型 pstmt.setDate(3, new java.sql.Date(emp.getHireDate().getTime())); pstmt.setInt(4, 1); return pstmt.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } finally { DBUtil.close(conn, pstmt, null); } }这里的关键点有两个。一是setDate必须用java.sql.Date不能用java.util.Date直接传否则类型不匹配会报错。二是finally块里统一关闭资源不能只在try里关——如果executeUpdate抛异常close代码根本不会执行到连接就泄漏了。连接池的连接泄漏是慢性的不会立刻报错但跑几个小时后就报Connection is not available遇到这个问题先查是不是有连接没关。查询员工时涉及多表关联员工表 join 部门表查出部门名称。SQL 写好后用ResultSet逐行读取封装成Employee对象。ResultSet的next()是移动游标的唯一方法first()、last()在老驱动里也能用但不要在next()循环里再调next()这是最常见的逻辑错乱原因——判断一次、读取一次、再用next()判断一次就会跳行。3.3 工资计算与事务控制批量发放工资时如何保证数据不半途而废工资发放是整个系统最需要事务的地方。业务逻辑是选定月份 → 遍历所有在职员工 → 按员工的基础数据计算工资 → 插入工资表。如果在遍历到第 50 个员工时数据库报错前面 49 条已经写进去了这就是部分成功用户重试时会发现重复数据。解决这个问题靠的是事务的三个操作setAutoCommit(false)关闭自动提交、commit()统一提交、rollback()遇到异常回滚。代码如下public boolean batchGenerateSalary(String month, double baseRate) { String queryEmp SELECT emp_id, base_salary, position_salary FROM employee WHERE status 1; String insertSalary INSERT INTO salary (emp_id, salary_month, base_salary, position_salary, allowance, deduction, actual_salary) VALUES (?, ?, ?, ?, ?, ?, ?); Connection conn null; PreparedStatement empStmt null; PreparedStatement salaryStmt null; ResultSet rs null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交 empStmt conn.prepareStatement(queryEmp); salaryStmt conn.prepareStatement(insertSalary); rs empStmt.executeQuery(); int count 0; while (rs.next()) { int empId rs.getInt(emp_id); double base rs.getDouble(base_salary); double position rs.getDouble(position_salary); double allowance 500.0; // 固定津贴实际项目中可查规则表 double deduction 0.0; double actual base position allowance - deduction; // 注意这里用同一个 PreparedStatement 反复 set 参数不能关闭 salaryStmt.setInt(1, empId); salaryStmt.setString(2, month); salaryStmt.setDouble(3, base); salaryStmt.setDouble(4, position); salaryStmt.setDouble(5, allowance); salaryStmt.setDouble(6, deduction); salaryStmt.setDouble(7, actual); salaryStmt.addBatch(); count; if (count % 100 0) { salaryStmt.executeBatch(); // 每 100 条刷新一次防内存溢出 } } salaryStmt.executeBatch(); conn.commit(); // 全部成功后统一提交 return true; } catch (SQLException e) { // 回滚到事务开始前的状态 if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); } catch (SQLException ignored) {} } DBUtil.close(conn, empStmt, rs); } }这段代码里addBatch()和executeBatch()是批量插入的标准写法。executeBatch会把多条 SQL 一次性发给 MySQL比单条executeUpdate快很多。但要注意Connection是同一个salaryStmt也是同一个不能在循环里close再重新创建否则前面的 batch 会被清空。实际开发里工资计算规则可以抽一个SalaryCalculator接口把个税计算、社保扣除做成可替换的策略类这样扩展不同的工资方案不用改主流程。事务隔离级别在这个场景用默认的REPEATABLE_READ就够了InnoDB 的行锁在批量更新时能避免并发问题。但如果你在while循环里执行了SELECT ... FOR UPDATE之类的手动加锁语句注意一定要在finally里把连接状态恢复否则连接池里的连接会带着未提交的事务还回去下一个人拿到这个连接就莫名奇妙的“锁表”了。4. 从完整代码到可演示系统界面、权限和初始化数据的最小闭环4.1 用 Swing 搭一个能用的登录与主界面而不是捂在控制台里课设验收时老师不会去看你的控制台输出他要看图形界面。Swing 虽然老但它是 JDK 自带的不需要额外引入 UI 依赖JavaSE 课设用它最稳妥。如果你对 JavaFX 更熟也行但 Swing 的兼容性最好——只要有 JDK 8 以上就能跑不会因为缺 JavaFX 库而启动失败。登录界面做一个最简版本JTextField输入用户名JPasswordField输入密码点击登录按钮后调用UserDao.login(username, password)。密码验证时把输入的密码做 MD5 后再与库里的比对public boolean login(String username, String password) { String sql SELECT user_id, role FROM sys_user WHERE username ? AND password ?; String hashed MD5Util.md5(password); // 自写的 md5 工具类 try (Connection conn DBUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, username); pstmt.setString(2, hashed); try (ResultSet rs pstmt.executeQuery()) { return rs.next(); } } catch (SQLException e) { e.printStackTrace(); return false; } }登录成功后的主界面布局建议用JSplitPane分左右两栏左侧是JTree菜单员工管理、部门管理、工资管理、系统设置右侧是JTabbedPane的内容区。菜单权限通过登录用户的role字段控制——admin能看到所有菜单employee只能看到工资查询。按钮的setVisible(false)配合role判断就能实现简单的权限隔离。界面代码量不小但别在这上面花太多时间。核心逻辑在 DAO 层界面只是调用 DAO 的方法。一个常见误区是把 SQL 写在按钮的ActionListener里上千行代码全在一个类里。正确做法是分三层model放实体类dao放数据库操作view只负责展示和调用 DAO。这样代码清晰老师问起来也敢说“我用了分层设计”。4.2 初始化数据脚本建库、建表、插入测试数据一次性跑完给老师演示时最怕现场建库失败提前准备一个全量 SQL 脚本是必须的。脚本文件init_db.sql按顺序包含CREATE DATABASE、CREATE TABLE、INSERT INTO初始数据。用命令行执行最简单mysql -u root -p init_db.sql脚本开头要加上DROP DATABASE IF EXISTS salary_db; CREATE DATABASE salary_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE salary_db;DROP DATABASE IF EXISTS是课设演示前的“后悔药”——数据乱了就重新执行一遍所有数据回到初始状态。测试数据至少包含 4 个部门、10 个员工、3 条工资记录。部门数据样例INSERT INTO department (dept_name, dept_manager) VALUES (技术部, 张伟), (人事部, 李娜), (财务部, 王强), (市场部, 赵敏);员工和工资的关联要有真实感技术部有 3 人工资里基本工资设置成不一样的数字这样演示查询、修改、删除时才看得出效果。管理员账号初始化一条INSERT INTO sys_user (username, password, role) VALUES (admin, MD5(123456), admin), (zhangsan, MD5(123456), employee);注意 MySQL 的MD5()函数返回的是 32 位十六进制字符串和 Java 里MessageDigest.getInstance(MD5)计算后转十六进制的结果一致这样程序登录才能匹配上。如果你在 Java 端对密码做了加盐那 SQL 脚本里也要用同样的规则写清楚最好直接在脚本里注释说明加盐逻辑不然以后自己都忘了。4.3 演示路径规划从登录到查看月度工资报表五分钟讲完核心功能课设答辩时间通常只有 5~10 分钟演示要有主次。建议按这条路径走登录顺带提一句密码是 MD5 存储的展示安全意识→ 进入员工管理新增一个员工展示增→ 查询员工列表看到新员工出现展示查→ 修改这个员工的岗位津贴展示改→ 打开工资管理生成本月工资展示事务批处理→ 按部门汇总查看工资总额展示聚合查询→ 删除刚才新增的员工展示删且说明为什么离职员工不物理删除。这条路径把增删改查全部覆盖了而且每一步都有业务含义。演示前一定要先自己走一遍重点检查两个地方一是新增员工后立刻生成工资新员工必须在工资表里出现二是删除有工资记录的员工时程序要提示“该员工存在历史工资记录已改为离职状态”而不是直接报外键错误。5. 常见问题避坑指南从环境配置到并发写入的五个翻车现场5.1 现象一java.sql.SQLException: Unknown system variable query_cache_size连接 MySQL 8.0 时用旧版本驱动5.x 的com.mysql.jdbc.Driver或旧版 HikariCP 会报这个错。原因是 MySQL 8.0 移除了查询缓存但旧驱动连接时还会去设置这个系统变量。解决升级驱动到mysql-connector-j8.0.xDriver类名换成com.mysql.cj.jdbc.DriverHikariCP 用到 4.0 版本。如果项目用的是老驱动且不想换可以临时在 JDBC 连接串里加query_cache_size0假参数绕过但治标不治本撑过验收可以后续开发必翻车。5.2 现象二中文乱码存进去是“???”或读出来是乱码这个问题的根源几乎都是字符集不一致。常见链条是数据库utf8mb4→ 连接串characterEncodingutf8mb4→ 页面或控制台读取时默认GBK。解决三步统一字符集。第一步确认建库语句带DEFAULT CHARACTER SET utf8mb4第二步JDBC 连接串里加characterEncodingutf8mb4注意不是utf8第三步如果还需要在 Windows 控制台打印中文运行 Java 时加 JVM 参数-Dfile.encodingutf-8。MySQL 8.0 还有一点如果characterEncoding写在serverTimezone前面部分驱动会忽略前者所以参数顺序也有讲究。5.3 现象三Cannot load driver class: com.mysql.jdbc.Driver或者运行时ClassNotFoundException原因通常是两种一是mysql-connector-java.jar没放进lib目录或没被 IDE 引用二是驱动类名写错8.0 的包名从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver。解决先在 IDE 里确认外部库里有这个 jar然后在db.properties里改成com.mysql.cj.jdbc.Driver。如果项目是用 Maven 构建的pom.xml里确认依赖坐标和版本groupId是com.mysqlartifactId是mysql-connector-j别再用旧的mysql-connector-java。5.4 现象四跑一段时间后报Connection is not available, request timed out连接池的连接泄漏了。最典型的原因finally块里没有关闭连接或者把Connection传给了某个方法但那个方法里没有关闭。HikariCP 30 秒拿不到连接就会抛这个异常前期不报数据库的max_connections被耗尽后必现。解决全局搜索DBUtil.getConnection()逐个检查finally里是否关闭。有一个技巧在DBUtil.close()方法里打一条日志或加断点观察哪个类拿的连接没有对应关闭。更狠的办法是给连接池加connectionInitSql参数或者临时用Druid的监控页面看活跃连接数——用 Druid 的话在浏览器里能看到activeCount字段排错非常直观。5.5 现象五批量插入工资时主键冲突或重复数据重试后月度工资翻倍原因有二一是没有先查这个月的工资是否已生成就执行插入二是事务控制里addBatch()执行后没有commit()用户以为失败又点了一次实际上里面一批已经写进去了。解决生成工资前先执行一次校验查询SELECT COUNT(*) FROM salary WHERE salary_month ?大于 0 就提示“本月工资已生成是否覆盖”。如果要支持覆盖就开事务先DELETE FROM salary WHERE salary_month ?再执行批量插入。这个 DELETE 和 INSERT 必须在同一个事务里否则删了但没插入成功当月的工资数据就空了。覆盖后建议看一眼SELECT COUNT(*) FROM salary WHERE salary_month ?确保和在职员工数对得上。6. 验证方法、测试数据与扩展方向课设做完之后还能往哪走验证一个工资系统不是点两下按钮就行要按三条线查数据正确性、并发安全性和权限边界。数据正确性方面用SELECT聚合查询对比“应发总和”和“实发总和”是否一致并发安全性方面开两个窗口同时对同一个月生成工资看会不会出现重复数据——如果你的代码里有校验查询第二次应该会拦截权限边界方面用employee账号登录确认看不到员工管理菜单。-- 验证某月工资总额是否与明细一致 SELECT salary_month, SUM(base_salary position_salary allowance - deduction) AS total_calc, SUM(actual_salary) AS total_actual FROM salary WHERE salary_month 2025-06 GROUP BY salary_month;如果total_calc和total_actual不一致说明程序里的计算逻辑和 SQL 里的聚合逻辑有偏差逐条排查员工数据。扩展方向上最推荐加一个“工资历史回溯”报表输入员工 ID 和月份区间展示该员工每个月的各项工资变化。这个功能在课程设计里非常有亮点它要用到GROUP BY和ORDER BY而且体现你对历史数据建模的理解——工资表按月份存就是为这类查询服务的。再加一个部门工资汇总的饼图或柱状图用 JFreeChart 画出各部门工资占比视觉效果直接拉满。我的习惯是等全部功能写完后再把建表 SQL 跑一遍把初始化数据恢复然后按验收路径走三遍每次都用不同的测试数据组合。这能提前暴露“某个月工资记录删除失败”“离职员工出现在工资生成列表”等边界问题。这个习惯救过我很多次希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑