ARTICLE DETAIL

资讯详情

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

Qt+MySQL学生信息管理系统实战:环境搭建、信号槽与QSqlTableModel

Qt+MySQL学生信息管理系统实战:环境搭建、信号槽与QSqlTableModel 简介这是一份面向计算机相关专业学生与C初学者的实践型项目源码以Qt作为图形界面框架、MySQL作为数据存储后端完整实现学生信息管理系统可作为课程设计或毕业设计的参考方案。压缩包共105个文件约14.75MB以cpp与h源文件为主配合ui界面文件、png图标资源、sql建库脚本、pro工程文件以及docx设计文档、pptx演示文稿和md说明覆盖从编码到答辩的完整链路。项目模块划分清晰包含学生信息管理、课程管理、选课管理、成绩管理、成绩统计与导出、密码重置等典型功能便于读者理解界面与数据库交互的完整流程。目前已有493人学习适合希望提升C编程、GUI设计与数据库应用综合能力、并需要真实项目经验的学习者参考借鉴。1. 从一份“学生信息管理系统”说起QtMySQL 这套组合到底能扛什么活很多人第一次接触 C 课程设计拿到的题目就是学生信息管理系统。需求看着简单增删改查、成绩统计、按学号排序。但真动手才发现命令行版本交上去老师嫌界面丑纯文件存储又扛不住并发和模糊查询。这时候 Qt MySQL 的组合就冒出来了——Qt 负责桌面端界面和事件循环MySQL 负责持久化和事务C 把两边粘起来。这套方案能解决的核心问题就三个界面能看、数据不丢、查询能快。适合谁适合正在做课程设计、想拿一个能写进简历的桌面项目练手的 C 学习者也适合已经会写控制台程序、想补上 GUI 和数据库这两块短板的开发者。我见过太多人卡在环境配置上就放弃了其实真正难的不是语法是 Qt 的信号槽怎么和数据库查询结果对接、MySQL 的字符集怎么不把中文变成问号。这篇就把这条链路从头到尾走一遍包括我踩过的那些坑。2. 环境搭建Qt 5.15.2 与 MySQL 的版本咬合关系2.1 为什么版本选错会直接编译失败Qt 和 MySQL 的搭配不是随便装两个最新版就能跑通的。Qt 的 MySQL 驱动QSqlMySQL在官方安装包里默认是不带的因为 MySQL 的客户端库涉及许可问题。你装完 Qt 5.15.2 之后在Qt/5.15.2/mingw81_64/plugins/sqldrivers/目录下大概率只能看到qsqlite.dll、qsqlodbc.dll没有qsqlmysql.dll。这意味着你写QSqlDatabase::addDatabase(QMYSQL)的时候程序会告诉你“Driver not loaded”。解决办法有两条路。第一条是自己编译驱动需要 Qt 源码、MySQL 的 C 连接器libmysql.dll 和对应的头文件用 qmake 编译出 qsqlmysql.dll。第二条是直接用别人编译好的驱动但版本必须和你的 Qt 编译器套件完全一致——MinGW 编译的 Qt 只能用 MinGW 编译的驱动MSVC 的只能用 MSVC 的。我一般推荐第一条路虽然麻烦但可控。具体操作先确认你的 Qt 套件。打开 Qt Creator在“工具 → 选项 → Kits”里看编译器是 MinGW 还是 MSVC是多少位的。然后去 MySQL 官网下载 MySQL Community Server安装时勾选“Client C API library”或者单独下载 Connector/C。把libmysql.dll和libmysql.lib放到 Qt 的 bin 目录或者项目输出目录。# 查看 Qt 安装目录下的 sqldrivers 插件 ls $QTDIR/plugins/sqldrivers/ # 如果只有 qsqlite.dll说明 MySQL 驱动缺失 # 编译驱动需要进入 Qt 源码的 mysql 插件目录 cd $QTSRC/qtbase/src/plugins/sqldrivers/mysql # 用 qmake 生成 Makefile注意指定 MySQL 头文件和库路径 qmake INCLUDEPATHC:/mysql-connector-c/include LIBSC:/mysql-connector-c/lib/libmysql.lib mingw32-make mingw32-make install这段命令的逻辑是进入 Qt 源码中 MySQL 驱动的插件目录用 qmake 传入 MySQL 连接器的头文件路径和库文件路径然后编译安装。参数说明INCLUDEPATH指向 MySQL 头文件所在目录LIBS指向libmysql.lib的完整路径。编译成功后qsqlmysql.dll会被复制到 Qt 的插件目录。注意如果你用的是 MSVC 套件把mingw32-make换成nmake并且确保 MySQL 连接器也是 MSVC 版本编译的。2.2 MySQL 安装时三个必须改的默认项MySQL 安装过程中有几个默认设置会直接影响后续开发。第一个是字符集。MySQL 8.0 默认字符集是utf8mb4这没问题但排序规则默认是utf8mb4_0900_ai_ci在某些旧版 Qt 驱动下会出现中文乱码。我一般会在创建数据库时显式指定CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci。第二个是 root 密码安装时设一个你记得住的别用空密码否则 Qt 连接时可能因为认证插件问题报Access denied。第三个是服务名默认是 MySQL80如果你机器上已经装过 MySQL 5.7服务名会冲突安装程序会提示你改端口或服务名。安装完成后用命令行验证一下-- 登录 MySQL检查字符集配置 SHOW VARIABLES LIKE character_set%; -- 创建项目数据库显式指定字符集 CREATE DATABASE student_mgr CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 创建一张学生表注意主键和索引 CREATE TABLE students ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, gender ENUM(男,女) DEFAULT 男, age INT CHECK (age BETWEEN 15 AND 60), major VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表语句里几个关键点student_no加了唯一索引防止学号重复gender用 ENUM 限制取值范围age加了 CHECK 约束created_at自动记录插入时间。引擎用 InnoDB 是为了支持事务和外键后面如果要加成绩表关联学生 IDInnoDB 是必须的。字符集在库和表两个层级都指定避免继承默认值带来的不确定性。2.3 Qt 项目文件里必须加的那几行新建 Qt Widgets Application 之后.pro文件里默认只有QT core gui。要连 MySQL必须加上QT sql。如果用的是 Qt 5.15.2还要确认greaterThan(QT_MAJOR_VERSION, 4): QT widgets这行存在。完整的.pro文件头部大概是这样QT core gui sql greaterThan(QT_MAJOR_VERSION, 4): QT widgets TARGET StudentMgr TEMPLATE app CONFIG c11加完sql模块之后在代码里才能#include QSqlDatabase、#include QSqlQuery、#include QSqlError。如果编译时报undefined reference to QSqlDatabase::addDatabase八成是.pro里漏了sql。另外如果你用的是 MSVC 套件可能还需要在.pro里加LIBS -lmysql但 MinGW 套件一般不需要因为驱动是动态加载的。3. 从界面到数据库Qt Designer 画完窗口之后怎么接逻辑3.1 用 Qt Designer 拖出主窗口和三个核心控件Qt Designer 是 Qt Creator 自带的界面设计工具双击.ui文件就能打开。学生信息管理系统的主界面我一般这样布局顶部一个QLineEdit做搜索框旁边一个QPushButton写“查询”中间一个QTableWidget显示学生列表底部一排按钮添加、修改、删除、刷新。右侧可以放一个QGroupBox里面用QFormLayout排布学号、姓名、性别、年龄、专业的输入框。拖控件的时候注意命名。Qt Designer 默认给控件起名pushButton、lineEdit、tableWidget这些名字在代码里引用时毫无辨识度。我习惯改成btnSearch、editSearch、tableStudents、btnAdd这种带前缀的名字。改名的位置在右侧属性编辑器里找到objectName那一栏。这个习惯能省掉后面大量“这个按钮到底是哪个”的排查时间。布局用QVBoxLayout和QHBoxLayout嵌套。主窗口中央放一个QWidget里面套一个垂直布局垂直布局里依次放搜索栏的水平布局、表格、按钮栏的水平布局。这样窗口拉伸的时候控件会跟着缩放不会出现表格挤成一条线的情况。3.2 信号槽连接按钮点击怎么触发数据库查询界面画完之后在 Qt Creator 里右键某个按钮选择“转到槽”Qt 会自动生成一个槽函数声明和空实现。比如btnSearch的clicked()信号生成的槽函数叫on_btnSearch_clicked()。这个自动连接机制依赖控件命名规范on_控件名_信号名。如果你手动改过控件名槽函数名也要跟着改否则连接不上。查询逻辑我一般这样写void MainWindow::on_btnSearch_clicked() { QString keyword ui-editSearch-text().trimmed(); QSqlQuery query; // 使用 LIKE 做模糊查询注意参数绑定防止 SQL 注入 query.prepare(SELECT student_no, name, gender, age, major FROM students WHERE student_no LIKE :kw OR name LIKE :kw); query.bindValue(:kw, % keyword %); if (!query.exec()) { QMessageBox::warning(this, 查询失败, query.lastError().text()); return; } ui-tableStudents-setRowCount(0); // 清空旧数据 int row 0; while (query.next()) { ui-tableStudents-insertRow(row); ui-tableStudents-setItem(row, 0, new QTableWidgetItem(query.value(student_no).toString())); ui-tableStudents-setItem(row, 1, new QTableWidgetItem(query.value(name).toString())); ui-tableStudents-setItem(row, 2, new QTableWidgetItem(query.value(gender).toString())); ui-tableStudents-setItem(row, 3, new QTableWidgetItem(query.value(age).toString())); ui-tableStudents-setItem(row, 4, new QTableWidgetItem(query.value(major).toString())); row; } }这段代码的关键点query.prepare()加bindValue()是防 SQL 注入的标准做法不要用字符串拼接。LIKE :kw配合%keyword%实现模糊匹配。setRowCount(0)在每次查询前清空表格否则旧数据会残留。query.value(列名)比用索引更可读但要求 SELECT 语句里明确写了列名不能用SELECT *。参数说明editSearch是搜索框的 objectNametableStudents是表格的 objectName。QMessageBox::warning在查询失败时弹出错误信息query.lastError().text()会返回 MySQL 的具体报错比如“Table student_mgr.students doesnt exist”方便定位。3.3 添加和修改事务提交与主键回填添加学生信息的时候除了插入students表可能还要在scores表里初始化一条记录。这种跨表操作必须用事务否则插了一半失败会留下脏数据。void MainWindow::on_btnAdd_clicked() { QSqlDatabase::database().transaction(); QSqlQuery query; query.prepare(INSERT INTO students (student_no, name, gender, age, major) VALUES (:no, :name, :gender, :age, :major)); query.bindValue(:no, ui-editNo-text()); query.bindValue(:name, ui-editName-text()); query.bindValue(:gender, ui-comboGender-currentText()); query.bindValue(:age, ui-spinAge-value()); query.bindValue(:major, ui-editMajor-text()); if (!query.exec()) { QSqlDatabase::database().rollback(); QMessageBox::critical(this, 添加失败, query.lastError().text()); return; } // 获取刚插入的自增 ID int newId query.lastInsertId().toInt(); QSqlQuery scoreQuery; scoreQuery.prepare(INSERT INTO scores (student_id, course, score) VALUES (:sid, 未选, 0)); scoreQuery.bindValue(:sid, newId); if (!scoreQuery.exec()) { QSqlDatabase::database().rollback(); QMessageBox::critical(this, 初始化成绩失败, scoreQuery.lastError().text()); return; } QSqlDatabase::database().commit(); on_btnSearch_clicked(); // 刷新表格 }事务的边界是transaction()到commit()中间任何一步失败都走rollback()。lastInsertId()返回刚插入行的自增主键用来关联成绩表。注意QSqlDatabase::database()返回的是默认连接如果你用了多个连接要传连接名。4. 避坑与排查那些让程序当场翻车的瞬间4.1 中文变问号字符集从连接串到表的全链路现象界面上输入“张三”存进数据库变成“??”或者查询出来显示乱码。原因Qt 的 QString 内部是 UTF-16MySQL 连接层默认可能是 latin1中间某一层做了错误转换。解决在addDatabase之后、open()之前设置连接字符集。QSqlDatabase db QSqlDatabase::addDatabase(QMYSQL); db.setHostName(127.0.0.1); db.setPort(3306); db.setDatabaseName(student_mgr); db.setUserName(root); db.setPassword(your_password); db.setConnectOptions(MYSQL_OPT_SET_CHARSET_NAMEutf8mb4); if (!db.open()) { qDebug() 连接失败: db.lastError().text(); }setConnectOptions里的MYSQL_OPT_SET_CHARSET_NAMEutf8mb4是关键。另外MySQL 服务端的my.ini或my.cnf里character-set-server也要是utf8mb4。表创建时已经指定了字符集这三处一致才能保证中文不乱。4.2 Driver not loaded驱动文件放对了但就是加载不了现象QSqlDatabase: QMYSQL driver not loaded但qsqlmysql.dll明明在sqldrivers目录里。原因qsqlmysql.dll依赖libmysql.dll而libmysql.dll不在系统的 DLL 搜索路径里。解决把libmysql.dll复制到 Qt 的bin目录或者复制到你的.exe输出目录。用 Dependency Walker 或者lddLinux 下可以查看缺失的依赖。Windows 下如果还不行检查libmysql.dll的位数是否和 Qt 套件一致——64 位的 Qt 必须配 64 位的libmysql.dll。4.3 查询结果为空但数据库里明明有数据现象query.exec()返回 true但query.next()一直是 false。原因最常见的是连接到了错误的数据库或者表名大小写问题。MySQL 在 Linux 下默认表名区分大小写Windows 下不区分。如果你在 Windows 开发、Linux 部署Students和students会被当成两张表。解决统一用小写表名并且在连接后执行SELECT DATABASE()确认当前库。另一个原因是事务未提交其他连接看不到未提交的数据。4.4 表格刷新后排序错乱QTableWidget 的排序陷阱现象点击表头排序后再点“刷新”顺序又变回插入顺序。原因QTableWidget的排序是视图层面的setItem重新填充时如果没启用setSortingEnabled(true)排序状态会丢失。解决在填充数据前先setSortingEnabled(false)填充完再setSortingEnabled(true)。如果数据量大建议用QSqlTableModel配合QTableView让数据库负责排序性能更好。4.5 程序退出时数据库连接未关闭导致卡死现象关闭主窗口后进程还在后台或者下次启动时报“too many connections”。原因QSqlDatabase的默认连接在程序退出时没有显式关闭。解决在MainWindow的析构函数或者closeEvent里遍历所有连接并关闭。MainWindow::~MainWindow() { QSqlDatabase::database().close(); QSqlDatabase::removeDatabase(QSqlDatabase::defaultConnection); delete ui; }注意removeDatabase必须在所有QSqlQuery对象销毁之后调用否则 Qt 会警告“connection is still in use”。5. 进阶技巧用 QSqlTableModel 把增删改查代码量砍掉一半5.1 从手动填表格切换到模型视图前面用QTableWidget逐行setItem的写法在字段少的时候还行一旦表有十几列代码就变得又长又容易漏。Qt 提供了QSqlTableModel直接把数据库表映射成模型QTableView自动显示增删改自动同步到数据库。核心代码就三行model new QSqlTableModel(this, db); model-setTable(students); model-setEditStrategy(QSqlTableModel::OnManualSubmit); model-select(); ui-tableView-setModel(model);OnManualSubmit表示修改先缓存在模型里调用model-submitAll()才写回数据库。如果想改一行立刻生效用OnFieldChange或OnRowChange。select()执行查询setModel把模型绑到视图。表头显示的是数据库列名想显示中文用model-setHeaderData(1, Qt::Horizontal, 姓名)逐个设置。5.2 过滤和排序交给数据库做QSqlTableModel支持setFilter()和setSort()底层会翻译成 SQL 的 WHERE 和 ORDER BY。比如按专业筛选、按学号排序model-setFilter(major 计算机科学与技术); model-setSort(0, Qt::AscendingOrder); // 按第 0 列升序 model-select();这比在内存里遍历QTableWidget的 item 快得多尤其是数据量上千之后。注意setFilter里的字符串如果包含用户输入要自己做转义防止 SQL 注入。QSqlTableModel不提供参数绑定所以过滤条件里的单引号要替换成两个单引号。5.3 提交失败时怎么拿到具体错误submitAll()返回 false 的时候错误信息在model-lastError().text()里。常见错误是主键冲突或者非空约束。我一般会在提交前做一次校验把错误弹窗做得具体一点if (!model-submitAll()) { QSqlError err model-lastError(); QMessageBox::critical(this, 保存失败, QString(错误代码%1\n错误信息%2).arg(err.nativeErrorCode(), err.text())); model-revertAll(); // 回滚未提交的修改 }revertAll()把模型里的未提交修改丢弃恢复到上次select()的状态。这个“后悔药”在批量编辑时很有用用户改错了可以直接撤销。5.4 一个我用了三年的连接管理习惯数据库连接不要每次查询都addDatabase那样会创建多个连接MySQL 默认最大连接数是 151桌面程序一般用不到那么多但反复创建销毁会拖慢响应。我的习惯是在程序启动时创建一个默认连接全局复用。如果确实需要多线程访问数据库每个线程创建自己的连接因为QSqlDatabase不是线程安全的。连接名用线程 ID 或者业务名区分关闭时按名字移除。这个习惯让我少踩了很多“连接泄漏”和“跨线程访问”的坑。写到这里这套 Qt MySQL 的学生信息管理系统从环境到模型视图的链路基本就通了。我自己的经验是先把数据库连接和字符集调通再画界面最后接信号槽顺序反了会浪费很多时间在排查环境上。希望帮到你。本文还有配套的精品资源点击获取
返回列表