news 2026/10/14 7:38:51

数据库课程设计实战:Python+MySQL员工信息管理系统源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据库课程设计实战:Python+MySQL员工信息管理系统源码解析

简介:这是一份面向数据库课程设计的员工信息管理系统完整源码与配套文档,适合高校学生、开发初学者用于理解Python与MySQL的实际交互流程。系统涵盖员工/部门数据的增删改查、数据库表设计、SQL操作与基本用户界面,从需求分析到编码实现均有体现。压缩包共9个文件,以两个Python源码文件、两份Word课程设计报告、四张PNG设计图(E-R图、数据流图、系统界面图)及一份README说明为主体,整体约6.71MB,结构清晰便于对照学习。目前已有4343人学习下载,具有一定的参考热度。通过该资源可获取可运行的员工账号/信息管理系统代码、配套课程设计报告、数据库设计图表与部署运行说明,能帮助读者快速把握从数据库建模到Python编程实现的核心步骤,尤其适合作为课程设计或期末项目的直接参考。

1. 数据库课程设计:员工信息管理系统这笔源码值不值得折腾

每年课程设计季,总有同学拿着从各处扒来的 Python + MySQL 员工管理系统源码,双击运行一堆报错,或者程序跑起来但一答辩证人就懵。这套源码本质上是一个数据库课程设计的完整范本:用 Python 做业务层,用 MySQL 做存储层,覆盖了建库建表、增删改查、模糊搜索、部门统计这些课程设计必考的点。它的价值不在界面多花哨,而在把数据库设计和 Python 工程化的基础姿势教给你,拿到手能跑、能改、能讲。适合正在做数据库课设的学生,也适合想快速上手 PyMySQL 写业务系统的开发者。

我这里按“数据库设计 → Python 连库 → 业务功能 → 踩坑复盘”的路子,把整套源码掰开揉碎讲完,最后补一个答辩能加分的技巧。全文涉及的表结构、代码片段和参数都以这套源码为基准展开,你拿到手对照着改就行。

2. 数据库设计:三张表与外键的取舍,附带建表脚本

员工信息管理系统的核心在数据层,表结构设计决定了后续业务代码怎么写。网上不少课程设计源码习惯把员工、部门、账号全塞进一张表,查询是省事了,但数据冗余严重,部门改名要 UPDATE 多条记录,而且没法约束“某部门下必须存在员工”这类逻辑。这套源码采用三张表分离,我觉得是更贴近实际项目习惯的做法。

2.1 员工表、部门表、账号表:字段设计与类型选型

先看三张表的核心结构。员工表(employee)是主表,放员工的基本信息;部门表(department)用来存部门名称和编号,员工表通过部门编号引用它;账号表(user)单独存放登录用户的用户名和密码,把认证信息和业务数据隔离开。

-- 部门表:一个部门编号对应一个部门名称 CREATE TABLE department ( dept_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '部门编号', dept_name VARCHAR(50) NOT NULL UNIQUE COMMENT '部门名称', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表'; -- 员工表:通过 dept_id 关联部门,通过 emp_id 自增主键定位记录 CREATE TABLE employee ( emp_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '员工编号', emp_name VARCHAR(20) NOT NULL COMMENT '员工姓名', gender ENUM('男','女') NOT NULL COMMENT '性别', birth_date DATE COMMENT '出生日期', dept_id INT NOT NULL COMMENT '所属部门编号', hire_date DATE NOT NULL COMMENT '入职日期', salary DECIMAL(10,2) DEFAULT 0.00 COMMENT '月薪', phone VARCHAR(20) COMMENT '联系电话', address VARCHAR(100) COMMENT '家庭住址', CONSTRAINT fk_dept FOREIGN KEY (dept_id) REFERENCES department(dept_id) ON UPDATE CASCADE ON DELETE RESTRICT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工表'; -- 账号表:账号独立存放,避免把密码字段放进员工业务表 CREATE TABLE user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(30) NOT NULL UNIQUE COMMENT '登录账号', password VARCHAR(64) NOT NULL COMMENT '密码(建议存哈希值)', role ENUM('admin','user') DEFAULT 'user' COMMENT '角色' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';

说明一下几个字段的设计逻辑:

  • gender用 ENUM 类型,从数据库层面限制输入只能是“男”或“女”,比用 VARCHAR(2) 更严谨,代码里也少一层校验。
  • salary用 DECIMAL(10,2),这是货币标准做法,别用 FLOAT,不然精度丢失会在累计统计时暴露问题。
  • hire_date和birth_date用 DATE 类型,后面做年龄、工龄计算时函数可以直接处理,比存字符串省事。
  • dept_id的外键ON UPDATE CASCADE ON DELETE RESTRICT,意思是部门编号被更新时员工表跟着改,但部门下有员工时不允许直接删除部门。这个约束后面章节会讲到对应的报错现象。

2.2 外键约束与索引:查询性能和完整性的平衡

外键的真正作用:限制的是操作,保护的是数据一致性

外键不是摆设。这里ON DELETE RESTRICT决定了当你试图删除一个仍有员工关联的部门时,MySQL 会直接报错。初学的人觉得这个约束碍事,实际项目中它反而是保护伞,防止你把部门删了结果员工表里留一堆“孤儿记录”。代码里 DELETE 语句失败之后程序捕获异常提示“该部门下还有员工,无法删除”,用户就知道先去转移员工。

索引:谁该建索引,谁不该建

部门表的dept_name加了 UNIQUE,本身就是唯一索引,根据部门名查部门编号会走索引。员工表里查询频率最高的是dept_id,外键约束会自动为该列建索引;emp_name如果经常作为搜索条件,可以在建表后补充:

CREATE INDEX idx_emp_name ON employee(emp_name);

注意索引不是越多越好。写操作频繁的列、区分度低的列(比如性别),建索引不但没加速还可能拖慢 UPDATE 速度。这套源码只在姓名列上加了普通索引,属于“够用就行”的思路。

2.3 初始化数据:先造数据再写代码

代码调试需要基础数据。建完表之后,源码里带了一段初始化脚本,预置了几个部门、测试账号和若干员工记录。建议保留这段脚本,原因有两个:一是方便你随时把数据库还原到初始状态,二是写业务代码前先有数据,查询逻辑才有得测。

-- 初始化部门 INSERT INTO department (dept_name) VALUES ('技术部'), ('人事部'), ('财务部'); -- 初始化员工 INSERT INTO employee (emp_name, gender, birth_date, dept_id, hire_date, salary, phone) VALUES ('张三', '男', '1995-03-12', 1, '2020-06-01', 8500.00, '13800000001'), ('李四', '女', '1998-07-24', 2, '2021-09-15', 7200.00, '13800000002'), ('王五', '男', '1992-11-02', 3, '2019-04-20', 9800.00, '13800000003'); -- 初始化账号 INSERT INTO user (username, password) VALUES ('admin', 'e10adc3949ba59abbe56e057f20f883e');

密码字段这里用的是 MD5 摘要值,对应明文123456。真实项目里强烈建议改用 bcrypt 或 sha2 加盐,课程设计用 MD5 展示“密码不存明文”这个意识就够,答辩时提一句为什么不用明文,比被问到才支支吾吾好很多。

3. Python 连接 MySQL:PyMySQL 参数配置与连接管理

表结构定了,下一步就是让 Python 和 MySQL 建立连接。这套源码用的是 PyMySQL,选它不选 MySQLdb,是因为 PyMySQL 纯 Python 实现,安装省事,Windows 下不用编译 C 扩展,对课程设计环境友好。Python 3.8 以上版本对 MySQLdb 的支持也相对麻烦,PyMySQL 是当下更稳妥的方案。

3.1 连接参数详解:charset 与 autocommit 是两大命门

连接代码不长,但参数含义值得掰碎讲。看源码里的数据库连接模块:

import pymysql DB_CONFIG = { "host": "localhost", "port": 3306, "user": "root", "password": "your_password", "database": "employee_db", "charset": "utf8mb4", # 别写成 utf8,会漏掉生僻字和 emoji "autocommit": False # 手动控制事务,防止误操作直接落库 } def get_connection(): try: conn = pymysql.connect(**DB_CONFIG) return conn except pymysql.err.OperationalError as e: print(f"数据库连接失败: {e}") return None

几个参数的坑:

  • charset必须填utf8mb4,不要填utf8。MySQL 的utf8实际最多存 3 字节,遇到生僻字或 emoji 会报Incorrect string value错误,utf8mb4 才是完整的 UTF-8。这个坑几乎每个人都会踩一次。
  • autocommit=False意味着每次 DML 操作(INSERT/UPDATE/DELETE)都要显式conn.commit()才能生效。新手最常见的翻车就是执行完 SQL 不提交,程序不报错但数据库里没变化。先关掉自动提交,养成手动提交的习惯,再去理解事务机制。
  • database参数对应的是库名,连接前先用命令行确认库存在,否则报Unknown database。源码里的建库语句是CREATE DATABASE employee_db DEFAULT CHARSET utf8mb4,建议先执行建库再跑 Python。

3.2 线程安全与会话隔离:一个连接别处处复用

很多人写课程设计会犯的一个错是全局只建一个连接,所有函数轮着用。这在单线程控制台程序里问题不大,但如果涉及多线程或者 GUI 程序,多个操作交叉使用同一个连接会导致事务互相污染,常见现象是 A 查询的结果带着 B 未提交的脏数据。

这套源码的做法是每个业务函数内部独立获取和关闭连接,虽然会有一点连接开销,但安全性优先。如果你想优化性能,可以用连接池。Python 这边有个轻量做法,用DBUtils.PooledDB包一层:

from dbutils.pooled_db import PooledDB import pymysql pool = PooledDB( creator=pymysql, maxconnections=10, mincached=2, host="localhost", port=3306, user="root", password="your_password", database="employee_db", charset="utf8mb4", autocommit=False ) def get_conn(): return pool.connection()

注意PooledDB的参数和pymysql.connect不完全一样,数据库名用database而不是db,密码用password不是passwd,写混了会报TypeError: __init__() got an unexpected keyword argument。连接池适合多线程场景,控制台单线程程序不需要,知道有这层玩法即可。

3.3 游标设置:为什么用 DictCursor

源码里查询结果的返回形式值得留意。默认游标返回的是元组,字段顺序一乱代码就崩;用DictCursor返回字典,按字段名取值,代码可读性高很多。

def query_employees(): conn = get_connection() if conn is None: return [] try: cursor = conn.cursor(pymysql.cursors.DictCursor) cursor.execute("SELECT emp_id, emp_name, dept_id, salary FROM employee") rows = cursor.fetchall() return rows except pymysql.err.Error as e: print(f"查询出错: {e}") return [] finally: cursor.close() conn.close()

cursor.close()和conn.close()放在finally里是必须的,否则连接不会归还给 MySQL,长时间跑程序会报Too many connections。这个“用完就关”的习惯从课程设计阶段养成,以后进项目写接口也不会被连接泄漏折磨。

4. 业务代码实现:从菜单循环到参数化增删改查

连接层就绪后,核心业务代码就清晰了。这套源码用的是控制台交互——不依赖 GUI 库,逻辑直观,课程设计答辩时讲解成本也低。整体框架是“菜单循环 + 函数分发”,菜单显示功能编号,用户选择后进入对应模块,执行完回到主菜单。

4.1 控制台主程序框架:先搭骨架再填肉

def main(): while True: print("\n====== 员工信息管理系统 ======") print("1. 查询员工列表") print("2. 添加员工") print("3. 修改员工信息") print("4. 删除员工") print("5. 按姓名搜索员工") print("6. 按部门统计人数") print("0. 退出系统") choice = input("请输入功能编号:").strip() if choice == "0": print("感谢使用,再见!") break elif choice == "1": list_employees() elif choice == "2": add_employee() elif choice == "3": update_employee() elif choice == "4": delete_employee() elif choice == "5": search_employee() elif choice == "6": stat_by_dept() else: print("无效编号,请重新输入")

.strip()去掉用户输入两端的空格和换行,避免用户习惯性多敲空格导致分支匹配失败。这段代码里每个函数的具体实现还没写,但框架已经能跑。我习惯先把菜单链路搭好,再逐个填业务函数,这样每完成一个函数就能立刻测试,不用等所有代码写齐。

4.2 新增与查询:参数化 SQL 与结果集格式化

查询列表:把晦涩结果转成人话
def list_employees(): conn = get_connection() if not conn: return try: cursor = conn.cursor(pymysql.cursors.DictCursor) cursor.execute(""" SELECT e.emp_id, e.emp_name, e.gender, e.salary, d.dept_name, e.hire_date FROM employee e JOIN department d ON e.dept_id = d.dept_id ORDER BY e.emp_id """) rows = cursor.fetchall() if not rows: print("暂无员工数据") return print("\nID 姓名 性别 月薪 部门 入职日期") print("-" * 50) for row in rows: print(f"{row['emp_id']:<5} {row['emp_name']:<6} " f"{row['gender']:<4} {row['salary']:<8} " f"{row['dept_name']:<8} {row['hire_date']}") except pymysql.err.Error as e: print(f"查询失败: {e}") finally: cursor.close() conn.close()

这里的 JOIN 查询拿到了部门名称而不是dept_id,用户看到的信息才有意义。ORDER BY emp_id保证输出顺序稳定,不依赖数据库物理存储顺序。格式化用:<8控制左对齐宽度,表格对得整齐,答辩时截图也好看。

新增员工:参数化查询防 SQL 注入
def add_employee(): conn = get_connection() if not conn: return try: name = input("姓名:").strip() gender = input("性别(男/女):").strip() birth = input("出生日期(YYYY-MM-DD):").strip() dept_id = input("部门编号:").strip() hire = input("入职日期(YYYY-MM-DD):").strip() salary = input("月薪:").strip() phone = input("电话:").strip() address = input("地址:").strip() # 参数化 SQL,避免用户输入引号等内容拼进语句破坏语法 sql = """INSERT INTO employee (emp_name, gender, birth_date, dept_id, hire_date, salary, phone, address) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""" cursor = conn.cursor() cursor.execute(sql, (name, gender, birth, dept_id, hire, salary, phone, address)) conn.commit() print(f"添加成功,员工ID为 {cursor.lastrowid}") except pymysql.err.IntegrityError as e: conn.rollback() print(f"数据完整性错误: {e}") except pymysql.err.Error as e: conn.rollback() print(f"添加失败: {e}") finally: cursor.close() conn.close()

重点在cursor.execute(sql, (参数元组))这种写法。参数值由 PyMySQL 内部转义后再传给 MySQL,不管用户输入什么都只当作字符串字面值处理,不会变成新的 SQL 语句。网上很多课程设计源码用f"... {name}"直接拼接 SQL,输入一个'); DROP TABLE employee; --就能删表,答辩时这是送命题。commit 位置也要注意:必须在 execute 成功后执行,异常分支用conn.rollback()回滚,防止半截数据入库。

4.3 更新与删除:先查再改,给用户后悔药

修改员工:先展示当前信息,再选择性更新

更新操作最忌讳的是让用户重新输一遍所有字段——既麻烦又容易误覆盖。源码里的改法是先按 ID 查出当前记录,展示后再让用户输入新值,留空的字段保持不变。

def update_employee(): conn = get_connection() if not conn: return try: emp_id = input("请输入要修改的员工ID:").strip() cursor = conn.cursor(pymysql.cursors.DictCursor) cursor.execute("SELECT * FROM employee WHERE emp_id = %s", (emp_id,)) row = cursor.fetchone() if not row: print("员工不存在") return print(f"当前信息:{row['emp_name']} | {row['gender']} | 部门ID {row['dept_id']} | 月薪 {row['salary']}") name = input(f"新姓名(回车保持原值 {row['emp_name']}):").strip() or row['emp_name'] salary = input(f"新月薪(回车保持原值 {row['salary']}):").strip() or row['salary'] sql = "UPDATE employee SET emp_name = %s, salary = %s WHERE emp_id = %s" cursor.execute(sql, (name, salary, emp_id)) conn.commit() print("修改成功") except pymysql.err.Error as e: conn.rollback() print(f"修改失败: {e}") finally: cursor.close() conn.close()

输入or row['emp_name']的写法很实用:用户直接回车时输入为空字符串,空字符串在布尔上下文里是 False,于是or后面的原值被赋值,实现“回车不动”。注意cursor.execute后没有马上 commit,因为用户可能输到一半发现自己不想改了,此时 Ctrl+C 中断程序,未提交的 UPDATE 不会写入数据库,相当于“后悔药”。

删除员工:两条 SQL 的顺序决定成败
def delete_employee(): conn = get_connection() if not conn: return try: emp_id = input("请输入要删除的员工ID:").strip() cursor = conn.cursor() # 先确认存在 cursor.execute("SELECT 1 FROM employee WHERE emp_id = %s", (emp_id,)) if not cursor.fetchone(): print("员工不存在") return confirm = input(f"确认删除员工ID {emp_id} 吗?(y/n):").strip().lower() if confirm != 'y': print("已取消删除") return cursor.execute("DELETE FROM employee WHERE emp_id = %s", (emp_id,)) conn.commit() print("删除成功") except pymysql.err.IntegrityError as e: conn.rollback() print(f"删除失败,该员工可能有关联数据: {e}") finally: cursor.close() conn.close()

删除前先查一下是否存在,再二次确认,这是最基础的保护姿势。真出问题的时候往往不是 SQL 写错,而是没确认直接删了。另外这个模块里故意演示了外键约束下的删除失败会怎样,IntegrityError异常捕获后回滚并提示用户,不会让程序崩溃退出。

4.4 模糊搜索与分页:LIMIT 不是摆设

员工多了以后,一次性 SELECT 全部数据既不快也不便于展示,分页是基本要求。搜索场景用LIKE模糊匹配姓名,配合LIMIT分页。

def search_employee(): conn = get_connection() if not conn: return try: keyword = input("请输入姓名关键字:").strip() page = int(input("页码(从1开始):").strip() or "1") page_size = 5 offset = (page - 1) * page_size cursor = conn.cursor(pymysql.cursors.DictCursor) sql = """SELECT emp_id, emp_name, gender, salary, hire_date, dept_id FROM employee WHERE emp_name LIKE %s ORDER BY emp_id LIMIT %s OFFSET %s""" # LIKE 模式串也要参数化 cursor.execute(sql, (f"%{keyword}%", page_size, offset)) rows = cursor.fetchall() if not rows: print("没有匹配的记录或页码超出范围") return for row in rows: print(f"{row['emp_id']} | {row['emp_name']} | {row['gender']} | {row['salary']}") print(f"第 {page} 页,每页 {page_size} 条") except ValueError: print("页码必须为数字") except pymysql.err.Error as e: print(f"搜索失败: {e}") finally: cursor.close() conn.close()

值得说的细节:LIKE %s里的%放在参数里而不是 SQL 语句里,即f"%{keyword}%"。如果你在 SQL 里直接写LIKE '%{keyword}%',参数化就失效了,用户输入%通配符会匹配全部记录。int()转换可能抛ValueError,所以单独捕获了一次。分页的OFFSET从(page-1)*page_size计算,第一页页偏移为 0,这是标准的 MySQL 分页写法。

5. 避坑排查:编码、事务与认证插件三个翻车重灾区

这个章节的内容全是血泪经验。课程设计季最常见的求助消息不外乎“为什么中文乱码”“为什么数据不见了”“为什么连不上数据库”。把这几类高频问题按“现象 → 原因 → 解决”整理出来,你遇到的时候直接对号入座就行。

5.1 中文乱码:连接、表、终端三层都要查

现象:INSERT 中文后查询出来是???,或者直接报Incorrect string value: '\xE5\xBC\xA0...'。

原因:字符集不一致。MySQL 服务端默认字符集可能是latin1,表建库时没指定utf8mb4,Python 连接时 charset 参数没设置,三个位置只要有一个不对,中文数据就保不住。

解决:三层统一。建库语句写CREATE DATABASE employee_db DEFAULT CHARSET utf8mb4,表结构里字段类型带CHARACTER SET utf8mb4或者在表定义里指定DEFAULT CHARSET=utf8mb4,Python 连接的charset填utf8mb4。排查的时候先跑一句SHOW VARIABLES LIKE 'character%';看服务端默认值,再SHOW CREATE TABLE employee;看表定义,最后看 Python 连接参数,逐层排除。

5.2 数据“神秘消失”:commit 没执行

现象:程序执行增删改操作后提示成功,但重开程序或者另开一个终端查询,数据根本没变。

原因:autocommit=False的状态下执行 UPDATE/DELETE,事务未提交。MySQL 默认隔离级别下,当前连接能看到自己未提交的修改,但其他连接看不到。程序退出时未提交的事务自动回滚,表现就是“数据消失”。

解决:每次 DML 后执行conn.commit()。如果你把这一行注释掉,事务会一直挂着直到连接关闭。可以用一个“调试时必查三连”来定位:一看执行是否真的到了 MySQL(用SELECT ROW_COUNT()看影响行数),二看是否 commit,三看连接是否被意外关闭。我习惯把 commit 写在 execute 成功后的代码行里,而不是finally里,这样逻辑更清晰。

5.3 删除失败:外键约束的 RESTRICT 属性在拦路

现象:DELETE 一条部门记录时程序报Cannot delete or update a parent row: a foreign key constraint fails,明明部门记录存在却删不掉。

原因:设计表结构时ON DELETE RESTRICT限制了父表记录的删除。employee 表里还有员工的dept_id指向这个部门,MySQL 拒绝执行删除。

解决:先删除或转移该部门下的所有员工,再删部门;或者把外键约束改成ON DELETE SET NULL(但那样员工表的dept_id字段必须允许 NULL)。课程设计要求展示“有外键约束”的意识,所以保留 RESTRICT 是合理的,但你要能在答辩时说出这个报错是什么含义,以及业务上怎么处理。

5.4 日期比较的坑:字符串和 DATE 类型互转的边界

现象:想查“2023 年入职的员工”写成WHERE hire_date >= '2023-01-01',结果查出来了,但统计“入职满一年”的员工时数据总是不对。

原因:DATE 类型和字符串比较时 MySQL 会做隐式转换,'2023-01-01'能被正确解析,但如果传入'2023-1-1'这种格式,转换结果可能不符合预期。更隐蔽的问题是使用DATE_SUB或DATEDIFF函数时参数类型弄反。

解决:日期字符串统一用YYYY-MM-DD格式传参,最好不要依赖隐式转换。统计工龄用TIMESTAMPDIFF(YEAR, hire_date, CURDATE())而不是DATEDIFF去除 365,因为后者不处理闰年。代码里所有日期输入都校验正则后再进 SQL。

5.5 MySQL 8.0 认证插件出错:caching_sha2_password 连不上

现象:PyMySQL 报Authentication plugin 'caching_sha2_password' cannot be loaded或者连接时直接 1045 错误。

原因:MySQL 8.0 默认认证插件是caching_sha2_password,老版本的 PyMySQL 不支持。另一种情况是数据库用户表的插件和当前版本不匹配。

解决:升级 PyMySQL 到 1.0 以上版本(直接pip install -U pymysql);如果升级后仍连不上,用 MySQL 命令行把认证方式改回mysql_native_password:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;

这条是改你本机开发环境的操作,线上生产环境推荐直接升级客户端而不是降级插件。改完记得在 Python 连接参数里把密码也同步更新。

6. 进阶技巧:事务装饰器与日志回放,答辩前的自检清单

很多课程设计源码能跑,但代码质量一眼就看得出是拼出来的。如果想让这套员工管理系统在答辩时让导师眼前一亮,我建议加两个小而实用的工程化改造。

第一个改造是给所有数据库操作函数加一个“事务装饰器”,把 commit 和 rollback 从每个函数里抽出来。改造的收益很直接:新增业务函数时不用再记忆“这个函数需要 commit 吗”,统一由装饰器处理。代码大概长这样:

def transactional(func): def wrapper(*args, **kwargs): conn = get_connection() if not conn: return None try: result = func(*args, conn=conn, **kwargs) conn.commit() return result except pymysql.err.Error as e: conn.rollback() print(f"事务回滚,错误: {e}") return None finally: conn.close() return wrapper @transactional def add_employee_with_log(name, conn=None): cursor = conn.cursor() cursor.execute( "INSERT INTO employee (emp_name, dept_id, hire_date, salary) VALUES (%s, %s, %s, %s)", (name, 1, '2024-01-01', 6000) )

注意装饰后的函数签名多了conn参数,这是把连接对象传入被装饰函数的标准做法。事务装饰器的好处是事务边界清晰:函数任意位置抛出异常都会被捕获并回滚,不会出现某个分支漏掉 rollback 的隐患。如果你的源码规模更大,还可以用contextlib.contextmanager写一个with db_transaction():上下文管理器,效果类似。

第二个改造是日志输出。Python 自带的logging模块就可以,不用引第三方库。给数据库操作函数加上日志,每次增删改查都记录操作时间和影响行数,写到一个log/目录下。这个功能对答辩极度加分——导师问“如何排错”的时候,你可以现场演示打开日志文件,指出哪次操作在什么时间改了什么数据。

import logging logging.basicConfig( filename='db_operations.log', level=logging.INFO, format='%(asctime)s - %(message)s' ) def log_operation(action, detail): logging.info(f"{action} | {detail}")

在删除函数里调用log_operation("DELETE", f"emp_id={emp_id}"),执行完就能在日志文件里看到流水。这也是以后做真实项目的习惯:关键操作留痕比事后回忆可靠得多。

最后给你一份我每次跑这个系统的自检清单:启动前确认 MySQL 服务已启动且库存在;启动后先跑一次“查询员工列表”验证连接和字符集;然后做一轮完整的“增 → 查 → 改 → 删”测试确认 commit 没问题;最后再试一次搜索和部门统计。这套流程走完,最常见的四个坑(连接失败、乱码、数据不落库、外键报错)基本都能提前暴露。那之后我每次交付类似的课设代码,都会强制自己先走一遍这个链路,确定核心链路没毛病再谈其他功能。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 7:38:42

本地可跑的多模态嵌入模型EmbeddingGemma 2:原理、部署与图文检索实战

本地就能跑的多模态嵌入模型&#xff0c;EmbeddingGemma 2 值得上手一试做搜索、做RAG、做内容去重的小伙伴&#xff0c;最近应该都被各种大模型刷屏了。但我今天想聊的不是动不动就几百B的对话大模型&#xff0c;而是一个体积友好、本地就能跑的多模态嵌入模型&#xff1a;Emb…

作者头像 李华
网站建设 2026/10/14 7:38:16

Flexsim自动化立体仓库仿真建模与避坑全指南

简介&#xff1a;基于Flexsim的自动化立体仓库仿真设计毕业设计论文&#xff0c;面向物流工程、工业工程等专业本科生及仓储系统设计人员&#xff0c;围绕AS/RS自动化立体仓库的建模仿真与效率优化展开完整研究。资源为1份docx文档&#xff0c;压缩包整体约1.31MB&#xff0c;正…

作者头像 李华
网站建设 2026/10/14 7:37:50

C#标签打印与读码检查闭环:从ZPL指令到产线防错实现

码打错一个&#xff0c;代价有多大&#xff1f;我见过因为标签上的一行序列号和实物对不上&#xff0c;整批货被客户退回&#xff0c;产线停工半天&#xff0c;质量部门连夜拉数据排查的场景。问题往往不是打印机能坏到哪里去&#xff0c;而是没人能保证“打印机认为的”和“实…

作者头像 李华
网站建设 2026/10/14 7:37:33

从传统监控到业务服务管理:PIGOSS BSM如何让IT故障提前一小时感知

1. 从"今天服务挂了才知道"到"提前一小时就知道要挂"做运维这些年&#xff0c;最怕的不是问题有多难处理&#xff0c;而是问题发生后你才知道。等到用户打电话过来投诉&#xff0c;说业务打不开了、订单提交不了、系统卡成狗&#xff0c;你再登录服务器去看…

作者头像 李华
网站建设 2026/10/14 7:37:11

Hexo Butterfly主题写作指南:从Front Matter到标签外挂的排版优化

讲真的&#xff0c;Hexo 折腾到这个阶段&#xff0c;主题装好了、配置能跑了、部署也通了&#xff0c;很多人会突然发现一个问题&#xff1a;自己写出来的文章&#xff0c;怎么看都跟别人用 Butterfly 主题写出来的效果差一截。不是内容不行&#xff0c;也不是主题不行&#xf…

作者头像 李华