多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

数据库课程设计:学校工资管理系统的表结构设计与实现

数据库课程设计:学校工资管理系统的表结构设计与实现 简介这是一份面向数据库原理及应用课程设计的完整项目资料以学校工资管理系统为实战案例适合正在完成数据库课设或希望提升SQL Server应用能力的高校学生参考。压缩包共3个文件doc格式课程设计报告详细阐述了系统分析与设计全过程sql文件提供了可直接运行的SQL命令语句bak为数据库备份文件整个资源包仅243KB便于快速下载使用。目前已有11402人学习浏览属于高分课程设计作品。报告围绕系统分析、需求分析和数据库设计展开包括需求调研、数据模型优化、数据库结构定义、数据录入与处理等核心环节并配套完整SQL源码与备份数据既能帮助读者理解工资管理系统中员工信息、工资数据等模块的设计思路也可直接借鉴用于自己的课程设计项目。1. 数据库课程设计——学校工资管理系统先想清楚表结构再谈功能实现学校工资管理系统是数据库课程设计里出现频率最高的题目但多数人交上去的版本都栽在同一个地方功能看着齐全表结构却经不起细看。录入、修改、删除、查询全都有评审一问“你的工资数据是历史快照还是实时计算”“金额字段为什么用 FLOAT”就答不上来。这门课设计真正要考察的不是你会不会写 CRUD而是你能不能把现实业务抽象成关系模型再用 SQL 和事务保证数据正确。这篇文章从表结构设计讲起一直讲到工资计算逻辑、踩坑排查和答辩验证目标是让读者顺着步骤把系统从零搭起来课程设计能交自己也能讲清楚每一处设计理由。适合正在做课程设计的学生也适合想用工资系统练手 JDBC/事务/存储过程的开发者。2. 需求分析与数据建模把工资系统拆成 5 张核心表2.1 业务需求拆解工资系统到底要管什么工资管理系统表面上是“发工资”实际要管的业务对象有四个学校组织架构里的部门、在职的教职工、每个月生成的工资单、以及工资单里一条条的工资项目明细。教工归属某个部门工资单归属某个教工工资单明细对应具体的工资项基本工资、岗位津贴、绩效、养老保险、个人所得税等。这就是最核心的实体关系。围绕这四个对象功能需求基本落在五个方向基础信息维护部门增删改、教工入职调岗离职、工资项配置哪些项目计入应发、哪些计入代扣、月度工资录入与计算、多条件查询统计按部门、按月份、按教工、用户登录与权限控制。在做数据库设计前建议先把需求列成表再动手这能避免建完表才想起来“哦还要记录调薪历史”。我一般会拿一张纸画出实体和关系部门与教工是一对多教工与工资单是一对多工资单与工资明细是一对多。这是 ER 图的雏形也是后面建表的依据。工资单必须单独建表不要为了省事把工资字段塞进教工表。这一点是新手最常见的错误——把基本工资、绩效、扣款全挂在教工表上结果一个人发十个月工资就把历史数据覆盖了。这个系统的本质是一个“按月生成工资档案”的业务不是简单的字典维护。所以工资单表和工资明细表是核心教工和部门只是维度表。评审老师最看重“月度工资是否有历史快照能力”这个问题从表结构就能看出来后续在实现时也直接决定工资计算的正确性。2.2 概念模型与逻辑模型ER 关系到表结构的映射概念模型确定后映射到逻辑模型就顺理成章。我给出六张表的方案其中五张是核心表一张做密码重置留有余量部门表 department部门维度教工表 teacher员工维度工资单表 salary月度工资主表工资明细表 salary_item_detail工资项目明细用户表 user_account登录与角色调薪记录表 salary_adjustment记录每次基础工资变更工资单主表保存每个教工每个月的汇总金额明细表保存该月每个工资项目的单项金额。二者通过 salary_id 关联形成一对多关系。为什么要把工资项拆成明细表而不是直接在主表里加三十个字段因为不同学校的工资项配置不一样今年有“文明奖”明年可能取消用行存储比用列存储灵活得多。这也是数据库设计中“行转列”思路的典型应用场景评审老师问起来你能说出理由而不是只能说“网上都这么做”。教工表只保存当前有效的基础工资历史工资变更通过调薪记录表保存工资单明细表保存每次发放时的工资项快照。这样设计后“2019 年 3 月张三的基本工资是多少”这类问题就能准确回答因为明细表存的是发放当时的数据不受后续调薪影响。表结构完成后用 Navicat 或 Workbench 画图导出 ER 图课程设计报告里一般都会要求放这张图。2.3 建表 SQL字段类型选型和约束设计建表时有一个铁律金额字段一律用 DECIMAL禁止用 FLOAT 和 DOUBLE。这一条后面专门讲坑建表阶段就先定下来。另一个经验是为每张表都加一个自增主键 id不要用教工号或部门名做联合主键否则后续需求变更比如教工号规则调整会牵连所有外键约束。CREATE TABLE department ( id INT AUTO_INCREMENT PRIMARY KEY, dept_name VARCHAR(50) NOT NULL UNIQUE, manager_id INT NULL, is_active TINYINT DEFAULT 1 COMMENT 1在职 0停用, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE teacher ( id INT AUTO_INCREMENT PRIMARY KEY, emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT 教工号, dept_id INT NOT NULL, name VARCHAR(30) NOT NULL, position VARCHAR(30) COMMENT 职称或岗位, base_salary DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 当前基础工资, hire_date DATE NOT NULL, status TINYINT DEFAULT 1 COMMENT 1在职 0离职, FOREIGN KEY (dept_id) REFERENCES department(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE salary ( id INT AUTO_INCREMENT PRIMARY KEY, emp_no VARCHAR(20) NOT NULL, salary_month CHAR(7) NOT NULL COMMENT 格式YYYY-MM, gross_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 应发合计, deduct_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 代扣合计, net_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 实发合计, status TINYINT DEFAULT 0 COMMENT 0草稿 1已确认 2已发放, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_emp_month (emp_no, salary_month), FOREIGN KEY (emp_no) REFERENCES teacher(emp_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE salary_item_detail ( id INT AUTO_INCREMENT PRIMARY KEY, salary_id INT NOT NULL, item_name VARCHAR(30) NOT NULL COMMENT 工资项目名称, item_type TINYINT NOT NULL COMMENT 0应发 1代扣, amount DECIMAL(10,2) NOT NULL DEFAULT 0.00, FOREIGN KEY (salary_id) REFERENCES salary(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE user_account ( id INT AUTO_INCREMENT PRIMARY KEY, teacher_id INT NULL, username VARCHAR(30) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT user COMMENT admin/user, FOREIGN KEY (teacher_id) REFERENCES teacher(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE salary_adjustment ( id INT AUTO_INCREMENT PRIMARY KEY, teacher_id INT NOT NULL, change_date DATE NOT NULL, old_base DECIMAL(10,2) NOT NULL, new_base DECIMAL(10,2) NOT NULL, reason VARCHAR(255), FOREIGN KEY (teacher_id) REFERENCES teacher(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关键设计说明salary 表的唯一键 (emp_no, salary_month) 保证同一个教工同一个月份只能有一条工资单记录这是“数据不重复”的第一道防线。salary_item_detail 表用 item_type 区分应发项和代扣项计算时按类型分别聚合避免要在代码里用多个列名拼 SQL。department 不设外键约束因为外键一旦加错层级部门和用户互相引用后续删除会非常痛苦逻辑上通过 service 层保证即可。teacher 表的外键指向 department(id)如果担心删部门时被外键挡住可以在删除前先确认部门下没有教工下面避坑章节会解释。2.4 为什么用工资明细表而不是工资项固定列很多人图省事直接在 salary 主表里建 base_salary、post_allowance、performance_pay、insurance, tax……三十个字段查询时一条 SQL 全取出来看起来方便实际维护是灾难。学校工资项配置经常调整今年把“交通补贴”并进“岗位津贴”明年新加“教龄津贴”每次调整都要改表结构。用明细表方案工资项本身就是一行数据新增工资项不需要改表只需要在录入界面加一个下拉选项。还有一个数据层面的理由明细表让“某个工资项目的全校汇总”变得非常容易查询。用 SQL 按 item_name 和 salary_month 聚合就能得出某月全校绩效总金额、某月代扣公积金总额。如果工资项是固定列这类统计就得写十几个 CASE WHEN代码冗长且容易漏列。但要注意明细表方案会在写入时产生更多行数据一个月 200 名教职工就可能产生近 2000 条明细对课程设计体量完全不是问题查询时用索引覆盖即可。3. 项目落地技术选型与三层架构实现3.1 技术栈怎么选Java JDBC MySQL 是课程设计最稳妥的组合课程设计技术栈不求新求稳求“你讲得清楚”。最常见的组合是 Java JDBC MySQL控制台程序或 Swing 界面二选一。如果学校要求 Web 形式可以换成 Java Web 或轻量级的 Spring Boot但核心的 DAO 层设计思路是一样的。我建议至少把代码分成三层dao数据访问、service业务逻辑、ui界面交互包结构一打开就能让评审看出你有工程意识。不推荐直接用 MyBatis 或 Spring Data JPA除非你早就熟练。课程设计答辩时老师更关注你有没有理解 SQL 本身框架帮不了你回答“这条 UPDATE 的隔离级别是什么”。JDBC 写起来繁琐但你能看到每一次数据库交互遇到问题也更容易定位。数据库连接采用传统 JDBC用配置文件管理 URL、用户名、密码不要硬编码在代码里。这样评委问起“数据库换了怎么办”时能答出“改配置文件就行”。连接管理使用简单的单例模式课程设计不需要引入连接池但要注意用完后关闭资源否则频繁操作会把数据库连接耗尽。3.2 项目结构与数据库连接工具类先搭建项目骨架我一般按以下包结构组织src/main/java/com/example/salary/ ├── ui/ 用户界面类控制台或Swing ├── service/ 业务逻辑工资计算、录入校验 ├── dao/ 数据访问JDBC操作 ├── utils/ 工具类DBUtil、DateUtil └── entity/ 实体类对应数据库表包名不必纠结关键是分层清晰。实体类与数据库表字段一一对应dao 层只做增删改查service 层处理业务规则ui 层负责收集输入和展示结果。这样分工的好处是出 Bug 时一眼就能定位是 SQL 写错还是计算逻辑写错。最怕的是所有代码堆在一个类里三个方法过后自己都找不到哪里能改。数据库连接工具类是整个项目的地基package com.example.salary.utils; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/school_salary?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD your_password; private static Connection conn null; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws Exception { if (conn null || conn.isClosed()) { conn DriverManager.getConnection(URL, USER, PASSWORD); } return conn; } public static void close(Connection c, Statement s, ResultSet r) { try { if (r ! null) r.close(); } catch (Exception e) { e.printStackTrace(); } try { if (s ! null) s.close(); } catch (Exception e) { e.printStackTrace(); } try { if (c ! null) c.close(); } catch (Exception e) { e.printStackTrace(); } } }参数说明URL 里的 useSSLfalse 是因为本地开发 MySQL 一般不配置 SSL 证书少了这个参数连接时会报警告但不出错serverTimezoneAsia/Shanghai 在 MySQL 8 下必填否则报时区错误这是 MySQL 驱动升级后的一个经典坑。characterEncodingutf8 保证中文不乱码。用户名密码换成你自己 MySQL 的配置数据库名先通过命令行创建好CREATE DATABASE school_salary DEFAULT CHARSET utf8mb4再执行上一章建表 SQL。3.3 DAO 层实现工资单插入与唯一键冲突处理工资单保存是整个系统的核心写入操作。一次保存要同时写 salary 主表和 salary_item_detail 明细表这是一个典型的事务场景。用 JDBC 手动管理事务关闭自动提交、执行多条 SQL、成功提交、失败回滚。下面给出工资单插入的 DAO 代码这是课程设计里不可少的“事务演示点”。public void insertSalary(Entities.Salary salary, ListEntities.SalaryItem items) throws Exception { Connection conn DBUtil.getConnection(); PreparedStatement psMain null; PreparedStatement psItem null; ResultSet rs null; try { conn.setAutoCommit(false); // 开启事务 // 1. 插入工资主表 String sqlMain INSERT INTO salary (emp_no, salary_month, gross_amount, deduct_amount, net_amount, status) VALUES (?, ?, ?, ?, ?, ?); psMain conn.prepareStatement(sqlMain, Statement.RETURN_GENERATED_KEYS); psMain.setString(1, salary.getEmpNo()); psMain.setString(2, salary.getSalaryMonth()); psMain.setBigDecimal(3, salary.getGrossAmount()); psMain.setBigDecimal(4, salary.getDeductAmount()); psMain.setBigDecimal(5, salary.getNetAmount()); psMain.setInt(6, 0); // 新保存默认草稿状态 psMain.executeUpdate(); rs psMain.getGeneratedKeys(); int salaryId -1; if (rs.next()) { salaryId rs.getInt(1); } // 2. 插入工资明细 String sqlItem INSERT INTO salary_item_detail (salary_id, item_name, item_type, amount) VALUES (?, ?, ?, ?); psItem conn.prepareStatement(sqlItem); for (Entities.SalaryItem item : items) { psItem.setInt(1, salaryId); psItem.setString(2, item.getItemName()); psItem.setInt(3, item.getItemType()); psItem.setBigDecimal(4, item.getAmount()); psItem.addBatch(); } psItem.executeBatch(); conn.commit(); // 全部成功才提交 } catch (Exception e) { conn.rollback(); // 任何一步出错回滚 throw e; } finally { conn.setAutoCommit(true); DBUtil.close(conn, psMain, rs); DBUtil.close(null, psItem, null); } }这段代码的要点在事务边界。先执行主表插入拿到自增主键再批量插入明细表。executeUpdate 执行主表 SQLRETURN_GENERATED_KEYS 用来取自增 id。明细表用 addBatch executeBatch 批量执行减少数据库连接往返次数。最后在 finally 里把自动提交改回 true避免连接归还后事务状态影响下一次调用。中途任何一条明细插入失败都会触发 rollbacksalary 主表和明细表不会出现“有主表无明细”或“有明细无主表”的数据。评审老师看到事务处理一般都会加分。但要注意MySQL 的 InnoDB 引擎才支持事务建表语句里用了 ENGINEInnoDB如果建的是 MyISAM事务会静默失效rollback 不会报错但也不会回滚。3.4 Service 层业务逻辑工资录入前先做三项校验Service 层负责在写入前把数据校验好避免脏数据进库。写工资单时我做三个校验教工是否存在且在职、该教工该月份是否已有工资单重复录入、工资项目金额是否合法不为负数代扣项金额不超过应发合计。前两项校验用一条 SELECT 就能完成。public String validateBeforeSave(String empNo, String month, BigDecimal gross, BigDecimal deduct) throws Exception { // 1. 检查教工存在且在职 String checkEmpSql SELECT COUNT(*) FROM teacher WHERE emp_no ? AND status 1; // 2. 检查月份是否已存在工资单 String checkSalarySql SELECT COUNT(*) FROM salary WHERE emp_no ? AND salary_month ?; // 3. 代扣合计不能超过应发合计 if (deduct.compareTo(gross) 0) { return 代扣合计大于应发合计请检查工资项金额; } return null; // null 表示校验通过 }这段代码的亮点是用了 BigDecimal 的 compareTo 而不是大于号。BigDecimal 是对象不能用 直接比较数值大小这是 Java 操作金额时最容易犯的错误之一。compareTo 返回 -1、0、1 分别表示小于、等于、大于。重复录入的判断必须依赖 salary 表上的唯一键 uk_emp_month而不是只在代码里查一遍。原因很简单两个管理员同时给张三录 3 月工资两人都先查“没有”然后同时插入唯一键能保证只有一条成功另一个抛 DuplicateKeyException。这就是数据库层的最后一道防线代码校验在前唯一键兜底在后。4. 工资计算逻辑应发、代扣与实发的实现4.1 工资项配置应发项和代扣项的聚合规则工资计算的核心公式只有一行实发 应发合计 - 代扣合计。但“合计”从哪里来是程序设计的关键。我采用的方式是工资项配置表定义项目名称和类型录入工资时按项目填金额系统自动聚合计算两个合计。应发项一般包括基本工资、岗位津贴、绩效工资、课时补贴、班主任津贴代扣项包括养老保险、医疗保险、失业保险、住房公积金、个人所得税。计算逻辑用 SQL 聚合实现还是用 Java 循环实现我建议在 Java 里做。因为工资项明细在写入前是一个 List在内存里循环累加比再查一次数据库更直观也容易单测。如果走存储过程或 DAO 里直接写 GROUP BY逻辑会被打散到多个地方答辩时讲起来费劲。但查询已经存在的工资单时直接查 salary 主表的 gross_amount、deduct_amount、net_amount 即可不要从明细表临时聚合。这是空间换时间的思路明细表保证可追溯主表冗余汇总值保证查询高效。这个冗余设计是有意违反第三范式的评审老师问起来就说是“以查询性能为目标的反范式设计”。4.2 工资计算类实现BigDecimal 与舍入模式工资计算涉及金额不允许浮点数误差。所有计算用 BigDecimal舍入模式用 HALF_UP四舍五入保留两位小数。保险费和公积金一般有固定比例比如养老保险个人缴纳 8%但计算后可能出现 1234.567 这种数值必须统一舍入。import java.math.BigDecimal; import java.math.RoundingMode; import java.util.List; public class SalaryCalculator { private static final BigDecimal RATE_PENSION new BigDecimal(0.08); private static final BigDecimal RATE_MEDICAL new BigDecimal(0.02); private static final BigDecimal RATE_HOUSING new BigDecimal(0.12); public static BigDecimal calcPension(BigDecimal baseSalary) { return baseSalary.multiply(RATE_PENSION) .setScale(2, RoundingMode.HALF_UP); } public static BigDecimal calcNetSalary(BigDecimal gross, ListBigDecimal deductions) { BigDecimal totalDeduct BigDecimal.ZERO; for (BigDecimal d : deductions) { totalDeduct totalDeduct.add(d); } return gross.subtract(totalDeduct).setScale(2, RoundingMode.HALF_UP); } }舍入模式这里有一个玄学如果先用 0.08 乘完基本工资再舍入一次养老保险就少了半分钱全校几百号人加起来误差就大了。所以每次乘法后立即 setScale不要把舍入留到最后一步。保险项如果学校规定按“基数 × 比例”且结果四舍五入到分那每一笔都单独舍入再求和如果规定“先求和再舍入”顺序则相反。这个细节课程设计报告里最好写明因为它是现实业务和书本计算最常见的分歧点。4.3 SQL 查询按月、按部门、按人的多条件统计工资录完要能查得出来这是系统“有用”的直观体现。多条件查询我统一用动态 SQL拼接 WHERE 条件。最常见的统计需求是“某个月各个部门的应发合计、实发合计”。SELECT d.dept_name, COUNT(t.id) AS emp_count, SUM(s.gross_amount) AS total_gross, SUM(s.deduct_amount) AS total_deduct, SUM(s.net_amount) AS total_net FROM salary s JOIN teacher t ON s.emp_no t.emp_no JOIN department d ON t.dept_id d.id WHERE s.salary_month ? AND d.is_active 1 GROUP BY d.dept_name ORDER BY total_gross DESC;这条 SQL 里 JOIN 的顺序要注意salary 先 JOIN teacher 拿到部门 id再 JOIN department 拿到部门名称。如果部门表有 is_active 字段统计时一定要过滤停用部门否则容易把已撤销部门的历史教工也算进去。GROUP BY d.dept_name 后 SELECT 的列必须是聚合列或分组列MySQL 默认只开了 ONLY_FULL_GROUP_BY 才会报错如果没开也能查但逻辑上是错误 SQL换到别的数据库可能直接报错。开发时建议把 sql_mode 加上 ONLY_FULL_GROUP_BY逼自己写规范 SQL。另一个高频需求是“查某教工某个月的工资明细”这个直接查 salary_item_detail 表按 salary_id 关联回工资单主表就能拿到全部明细。注意明细表的 item_type 字段决定它是加项还是减项前端显示时可以分别放在两个区块。5. 常见问题与避坑金额精度、外键删除与死锁排查5.1 工资总额对不上FLOAT 浮点精度导致的翻车现场现象工资单录入时显示应发 10000.00代扣 2356.78实发却是 7643.209999999金额后面多出一串小数。用 Navicat 看表数据正常但在 Java 里 BigDecimal 转换后就是不对。原因salary 或 salary_item_detail 表里的 amount 字段用了 FLOAT 或 DOUBLE。MySQL 的 FLOAT 是单精度浮点类型存储 10000.01 这种小数时二进制表示不精确。SELECT 出来显示可能正常但一旦参与 SUM 或传到 Java 转 BigDecimal误差就暴露了。解决所有金额字段改为 DECIMAL(10,2)并在建表时确认没有历史遗留表。已经建了 FLOAT 字段的表用 ALTER TABLE 修改字段类型。5.2 删除部门报错外键约束和逻辑删除的取舍现象想删除一个已经没有人任职的空部门SQL 执行报“Cannot delete or update a parent row”因为 teacher 表里存在指向该部门的外键。实际该部门已经没有在职教工但历史离职教工还挂在那个部门 id 上。原因teacher.status 0 表示离职但离职教工的 dept_id 仍然指向原部门。外键约束在意的是“引用存在”不关心状态字段。只要有一条历史记录引用该部门物理删除就失败。解决不要物理删除部门。给部门表加 is_active 字段停用部门时执行 UPDATE department SET is_active 0 WHERE id ?。查询时默认加过滤条件 AND is_active 1。这样既保留历史数据又满足前端的“删除”操作语义。物理删除只用于“该部门从未分配过教工”的极端情况用一条子查询先确认无引用再删除。5.3 批量发放工资时数据库卡死事务死锁排查现象模拟一个院系同时给几十名教工确认工资单连续快速点击后程序卡住过一会儿报 Deadlock found。重启程序后恢复但用户不确定刚才有没有数据写入成功。原因多个事务同时操作 salary 表INSERT 的顺序不一致导致死锁。事务 A 先插张三再插李四事务 B 先插李四再插张三两个事务互相持有对方需要的行锁。课程设计里并发量很低但如果用了批量循环插入且不控制顺序依然会触发。解决在同一批次操作时让写入顺序统一比如按 emp_no 排序后再插入另外为事务设置超时时间避免无限等待。额外提醒连接关闭不彻底也会导致“卡死”假象。打开的 Connection、Statement、ResultSet 没有全部关闭数据库连接池被占满后续操作全部阻塞。用 DBUtil.close 统一管理资源后将代码里每处 JDBC 调用都包进 try-finally 即可。5.4 改了一个人的基础工资所有历史月份工资都变了现象年终调薪把张三的基本工资从 8000 改成 9000然后发现张三 1 月到 6 月的工资单里应发合计全部变成了新数字历史数据被“篡改”了。原因工资单明细表里没有存“发放当时的工资项金额”只是通过外键关联到 teacher.base_salary 实时取值。改教工表当前工资后历史明细跟随变化。解决工资明细表里存快照金额。insertSalary 时从 salary_item_detail 写入 amount该字段保存计算当时的数值不与教工表实时联动。调薪走 salary_adjustment 记录流水不改历史工资单。评审问这个问题基本就是想考察你有没有快照意识动态视图在报表系统里是大忌。5.5 数据库连接报时区错误和 SSL 警告现象用 MySQL 8 驱动时每次启动程序报“The server time zone value Öйú±ê׼ʱ¼ä is unrecognized”还有一串 SSL 证书警告虽然不影响运行但很碍眼。原因MySQL 8 连接驱动的默认时区机制变化URL 里没指定 serverTimezone 导致解析失败SSL 没配置时驱动默认尝试协商。解决URL 加上 serverTimezoneAsia/Shanghai 和 useSSLfalse。这个属于环境配置问题不属于业务 Bug但答辩时写在环境说明里会给评审留下“你踩过坑、处理过问题”的印象。6. 从“能跑”到“能交”用对账脚本和演示用例保证质量课程设计交上去前我建议花半天做三件事写一个对账脚本验证数据一致性、准备一组演示数据覆盖关键场景、把数据库备份脚本放进项目里让别人能一键建库。对账脚本的核心是验证“主表的汇总金额等于明细表的聚合金额”这是数据一致性的底线。SELECT s.id, s.salary_month, s.emp_no, s.gross_amount, COALESCE(d_sum.gross_sum, 0) AS detail_gross_sum, s.deduct_amount, COALESCE(d_sum.deduct_sum, 0) AS detail_deduct_sum FROM salary s LEFT JOIN ( SELECT salary_id, SUM(CASE WHEN item_type 0 THEN amount ELSE 0 END) AS gross_sum, SUM(CASE WHEN item_type 1 THEN amount ELSE 0 END) AS deduct_sum FROM salary_item_detail GROUP BY salary_id ) d_sum ON s.id d_sum.salary_id WHERE s.gross_amount COALESCE(d_sum.gross_sum, 0) OR s.deduct_amount COALESCE(d_sum.deduct_sum, 0);如果这条 SQL 查出任何记录说明有工资单的主表和明细不一致必须修复后再交。我自己的习惯是写完插入功能后先批量造 30 个人的数据再跑一次这个脚本确保没有一条对不上的记录。金额差 0.01 都不行因为工资系统里“差一分钱”会被无限放大。演示数据怎么选覆盖三个场景一个全职教师的完整工资单含所有代扣项、一个刚入职教师的工资单只有基本工资、一个月中有调薪记录的教师验证历史快照。这三个场景能展示系统的完整能力也能让评审看到你对特殊情况的处理。演示时不要拿一两百条真实数据一是插入时间长二是评审问“这条数据怎么来的”你答不上来。手工造的 20 条结构清晰的假数据反而是最好的。备份脚本放进项目根目录写成 bat 或 shell 脚本核心命令就一行用 mysqldump 定时导出数据这个操作要确保能恢复数据损失。mysqldump -uroot -p123456 school_salary backup_$(date %Y%m%d).sql恢复时执行 mysql -uroot -p123456 school_salary backup_20250601.sql 即可。课程设计答辩现场最难堪的时刻不是功能不行而是“我电脑重启了数据库没启动”。把备份脚本和恢复说明写进 README至少能展示工程完整度。最后提醒一个答辩技巧主动讲一个你踩过的坑。比如我不会说“我的系统很完美”而是说“早期我把金额字段设成 FLOAT 导致对账不平后来统一改成 DECIMAL 并加了数据一致性校验脚本”。这样既展示了项目质量也展示了排查问题的能力。有一次我带的模拟项目 X 就因为只讲了功能被追问数据一致性时卡了壳从那以后我每次都先讲设计取舍。以上这一套流程走下来课程设计能跑通评审的问题也基本都能兜住希望帮到你。本文还有配套的精品资源点击获取
返回列表