简介:基于SQL Server与Qt实现的学生管理系统,是面向计算机相关专业在校生、教师及企业开发者的课程设计与毕业设计参考源码。项目以C++/Qt搭建前端界面,后端对接SQL Server数据库,覆盖学生信息、家庭情况、民族与学校字典、金额操作等常见管理模块,代码经测试可正常运行,也可在此基础上二次开发,实现其他管理功能。压缩包共56个文件,以10个cpp源文件、9个h头文件和9个ui界面文件为主体,配合22张png界面截图、工程配置文件(pro/qrc/user)及README说明文档,整体约209KB,目录结构与模块对应清晰,适合边看代码边对照界面理解运行逻辑。目前已有112人学习下载,压缩包内附带完整源码、界面布局与运行说明,建议先阅读README,再结合截图还原项目全貌;无论用于课程设计、毕业设计还是入门学习,都能作为SQL Server与C++/Qt整合开发的实用范本。
1. 先别急着跑:这个 SQL Server + Qt 学生管理系统到底值在哪
这套源码打包里,真正值钱的不是那几个 UI 界面,而是把 SQL Server 和 Qt 拼接起来的那条完整链路。学生管理系统这个题目太常见了,常见到很多人的课设就是拿网上的 Python + MySQL 改一改;但如果你所在专业的课程设计、毕设指定了 SQL Server + C++,这套基于 Qt Widgets 的工程就正好对位:主窗口负责导航,addfamily、addschool、addbody、addmoney 这些表单分别录入家庭、学校、体检和缴费信息,确认删除和查询预览也都有对应模块。它适合三类人:刚学完 C++ 想看看真正工程怎么组织文件的人;正在做课设、毕设,需要一套能跑通增删改查底子的人;以及手头有 SQL Server 作业、想找个现成界面改功能的人。下面按“工程结构 → 搭环境 → 跑通 → 改功能 → 踩坑”的顺序拆,每一步都给出能直接照抄的做法。
2. 工程拆解:从 SQL.pro 到八个界面文件,先搞清楚谁在干活
拿到这份资源第一件事不是双击运行,而是先看文件结构。压缩包里除了一张 README.md 和一堆截图之外,真正的源码是 main.cpp、mainwindow.cpp/h、dialog.cpp/h,以及 addfamily、addschool、addrace、addbody、addmoney 五个带 .ui 的添加表单,外加 areyousure 确认框、inputview 查询视图和 images.qrc 资源文件。这属于典型的 Qt Widgets 单工程结构:一个 .pro 管所有源文件,一个主窗口做聚合,若干个 QDialog 做细分数据的录入。
2.1 文件清单与模块分工:哪些是壳、哪些是业务、哪些是资源
我把关键文件按职责拆了一张表,这样你拿到手能快速定位改哪里:
| 文件 | 类型 | 职责 |
|---|---|---|
| SQL.pro | 工程文件 | 声明 QT += widgets sql,列出所有源文件和 .ui |
| main.cpp | 入口 | new MainWindow,设置窗口标题后 show |
| mainwindow.cpp/h | 主窗口 | 数据库初始化、列表加载、导航跳转 |
| dialog.cpp/h | 通用对话框 | 放置公共逻辑,比如各类信息展示窗口 |
| addfamily.ui/cpp/h | 表单 | 家庭信息录入:家长姓名、电话、住址等 |
| addschool.ui/cpp/h | 表单 | 学校信息录入:校名、地址、联系人 |
| addrace.ui/cpp/h | 表单 | 民族信息维护,常用于下拉框数据源 |
| addbody.ui/cpp/h | 表单 | 体检信息录入:身高、体重、视力、日期 |
| addmoney.ui/cpp/h | 表单 | 缴费记录录入:金额、项目、缴费日期 |
| areyousure.ui/cpp/h | 确认框 | 删除前的二次确认,返回 Accepted/Rejected |
| inputview.ui/cpp/h | 视图 | 查询预览列表,主窗口里嵌的核心数据展示区 |
| images.qrc | 资源 | 打包窗口图标和按钮图片 |
模块之间的调用关系很直白:mainwindow 是最外层容器,按钮点击后通过 exec() 弹出 add 系列对话框,对话框内部完成 INSERT 或 UPDATE,关闭后主窗口再刷新 inputview。areyousure 是模态确认框,独立于任何业务表,好处是删除学号、删除缴费记录都能复用它,不用每个模块各写一个 QMessageBox::question。
这套组织方式最大的优点是把“界面”和“表结构”一一对应了。addfamily 对应 Family 表,addmoney 对应 Payment 表,你改表结构时能顺着文件找到所有受影响的地方,比那种十几张表共用一个巨无霸窗口的设计好维护得多。
2.2 从 add 系列界面反推 SQL Server 表结构:六张表怎么建
不用打开源码里的建表脚本,光看 add 系列的命名就能猜出数据库至少有这几张表:学生主表 Student,家庭表 Family,学校表 School,民族表 Nation,体检表 BodyInfo,缴费表 Payment。学生主表作为核心,通过学号关联其他表。建表脚本我按 SQL Server 的常用写法整理如下,字段名和界面上的中文标签要能对上:
CREATE DATABASE StudentDB COLLATE Chinese_PRC_CI_AS; GO USE StudentDB; GO CREATE TABLE Nation ( nation_id INT IDENTITY(1,1) PRIMARY KEY, nation_name NVARCHAR(50) NOT NULL ); CREATE TABLE Family ( family_id INT IDENTITY(1,1) PRIMARY KEY, father_name NVARCHAR(50), mother_name NVARCHAR(50), phone NVARCHAR(20), address NVARCHAR(200) ); CREATE TABLE School ( school_id INT IDENTITY(1,1) PRIMARY KEY, school_name NVARCHAR(100) NOT NULL, school_addr NVARCHAR(200), contact_tel NVARCHAR(20) ); CREATE TABLE Student ( stu_no NVARCHAR(20) PRIMARY KEY, stu_name NVARCHAR(50) NOT NULL, gender NVARCHAR(10), birthday DATE, nation_id INT, family_id INT, school_id INT, CONSTRAINT FK_Student_Nation FOREIGN KEY (nation_id) REFERENCES Nation(nation_id), CONSTRAINT FK_Student_Family FOREIGN KEY (family_id) REFERENCES Family(family_id), CONSTRAINT FK_Student_School FOREIGN KEY (school_id) REFERENCES School(school_id) ); CREATE TABLE BodyInfo ( body_id INT IDENTITY(1,1) PRIMARY KEY, stu_no NVARCHAR(20) NOT NULL, height DECIMAL(5,2), weight DECIMAL(5,2), vision NVARCHAR(20), check_date DATE, CONSTRAINT FK_BodyInfo_Student FOREIGN KEY (stu_no) REFERENCES Student(stu_no) ); CREATE TABLE Payment ( pay_id INT IDENTITY(1,1) PRIMARY KEY, stu_no NVARCHAR(20) NOT NULL, pay_item NVARCHAR(100), pay_amount DECIMAL(10,2), pay_date DATE, CONSTRAINT FK_Payment_Student FOREIGN KEY (stu_no) REFERENCES Student(stu_no) );建库那行 COLLATE Chinese_PRC_CI_AS 很关键,它可以避免后面界面中文全部变成问号的悲剧。字段选择上,学号用 NVARCHAR 而不是 INT,是因为很多学校的学号带字母和前缀,数字类型存不下;金额和身高体重用 DECIMAL 而不是 FLOAT,是因为浮点计算会产生 0.00001 这种尾差,做统计时很难看。外键约束我建议保留,虽然插入时效率略降,但能保证不会出现“体检记录指向一个不存在的学生”这种脏数据。
2.3 选型理由:为什么是 SQL Server 而不是 SQLite 或 MySQL
我知道很多人在网上抄的课设是 SQLite 或者 MySQL,但这份工程选择 SQL Server 是有现实原因的。机房和实验室的机器普遍装了 SQL Server,导师演示时用 SSMS 打开就能看数据,而 SQLite 只有一个文件,很多老师不认。对比一下三种方案的处境:
| 数据库 | 驱动名 | 典型坑 | 适合场景 |
|---|---|---|---|
| SQL Server | QODBC | ODBC 驱动版本、SSL 加密、实例名 | 课设、毕设、企业内网系统 |
| MySQL | QMYSQL | 驱动插件经常编译缺失,UTF-8 乱码 | Web 后台配套、Linux 服务器 |
| SQLite | QSQLITE | 多用户并发差,无账号权限 | 单机小工具、快速原型 |
Qt 侧通过 QSqlDatabase 抽象层操作数据库,addDatabase 第一个参数传的是 Qt 的驱动名,比如 QODBC、QMYSQL、QSQLITE。这里注意别混淆两个概念:“QODBC”是 Qt 框架内建的驱动插件,“ODBC Driver 17 for SQL Server”是微软提供的 ODBC 驱动程序,前者负责把 Qt 的 SQL 调用转成 ODBC 标准接口,后者才真正和 SQL Server 实例通信。连接串里写的是后者。
选 SQL Server 还有一个务实的好处:权限体系完整。你可以用 sa 账号只给这个库的 db_owner 权限,不暴露整个实例,这样交作业时不用把数据库密码也交出去。后面第 5 章我会专门说新版 ODBC 驱动因为 SSL 加密默认开启导致的连接失败,那是最容易让人怀疑人生的地方。
3. 把工程跑起来:Qt Creator、ODBC 驱动与 SQL Server 的最小配置
很多同学卡在编译这一步,其实不是代码问题,而是环境组合没对上。我建议的配置组合是:Qt 5.15.2 + MSVC2017_64 编译器 + SQL Server 2019/2022 Developer 版 + SSMS。Qt 版本和编译器必须匹配,这是第一条纪律;用 MinGW 套件编译虽然也能出界面,但遇到 QODBC 插件加载失败的概率明显更高,后面我会解释。
3.1 环境版本怎么选:为什么是 Qt 5.15.2 配 MSVC2017_64
Qt 版本和编译器套件是一一对应的。打开 Qt Creator 的 Kits 页面,你会看到 Desktop Qt 5.15.2 MSVC2017_64 和 Desktop Qt 5.15.2 MinGW 8.1.0 这类条目,它们用的是同一套 Qt 源码编译出的不同二进制,互相不能混用。这个工程既然要对接 SQL Server,我建议直接用 MSVC 套件,理由和 ODBC 插件有关:Qt 在 Windows 上自带的 QODBC 插件很多是用 MSVC 预编译的,MinGW 环境偶尔会碰到 plugins/sqldrivers 里找不到 qsqlodbc.dll 的情况,编译时也不报错,运行时才告诉你 driver not loaded,这个坑我在第 5 章详细说。
版本选择上,Qt 5.15.2 是目前兼容性最稳的 LTS 后期版本,网上能找到的资料最多。SQL Server 不用纠结具体版本,2019 和 2022 都行,Developer 版免费且功能和 Enterprise 一样。安装 SQL Server 时,建议把“数据库引擎服务”和“客户端工具连接”勾上,SSMS 单独安装,别用安装包里自带的精简工具。
建好环境后,用 Qt Creator 打开 SQL.pro,第一步先看工程文件里QT += core gui sql这一行有没有 sql。.pro文件里声明了 sql 模块,Qt 才会去链接 Qt5Sql.lib,否则编译时会出现一堆unresolved external symbol错误。我一般会顺手在QT +=那行后面加上widgets,虽然新工程默认有,但从网上下载的工程经常省略这行。
3.2 在 SSMS 里建库建表,先把数据层跑通
连接数据库之前,我习惯先在 SSMS 里把库和表建好,确认 SQL Server 本身没问题,再回头调 Qt 代码。这样做能减少变量:如果 Qt 连不上,问题只可能在 Qt 侧或 ODBC 配置侧,不用怀疑数据库没起来。
按第 2.2 节的脚本,在 SSMS 里新建查询执行一遍。执行完展开数据库看表是否都生成了。然后右键数据库,新建一个“ODBC 数据源测试”,打开 Windows 的 ODBC 数据源管理器,选“系统 DSN”,添加一个 SQL Server 或 ODBC Driver 17 for SQL Server 的条目,服务器填127.0.0.1,1433,数据库选 StudentDB,测试连接。这里能通,说明数据库层面没问题。
连接测试有个细节:如果你的 SQL Server 是默认实例,服务器可以填.或者localhost;如果是命名实例,要写成机器名\实例名或者ip,端口的形式。很多人在这一步填错,Qt 报的错却非常抽象,一度让我以为是自己代码写错了。顺手验证一下服务是否启动:Win+R 输入 services.msc,找到 SQL Server (MSSQLSERVER),状态应该是“正在运行”。
3.3 修改连接参数并完成首次编译
源码里数据库连接多半写在 mainwindow.cpp 或 dialog.cpp 的构造函数里,下断点或者直接搜 QSqlDatabase 就能找到。我一般会把它抽成一个独立函数,方便调试:
#include <QSqlDatabase> #include <QSqlError> #include <QDebug> bool initDatabase() { QSqlDatabase db = QSqlDatabase::addDatabase("QODBC"); db.setDatabaseName( "Driver={ODBC Driver 17 for SQL Server};" "Server=127.0.0.1,1433;" "Database=StudentDB;" "Trusted_Connection=Yes;" "Encrypt=no;" "TrustServerCertificate=yes;"); if (!db.open()) { qDebug() << "open database error:" << db.lastError().text(); return false; } return true; }这段代码里,addDatabase("QODBC")指定 Qt 侧驱动,setDatabaseName传的是一整条 ODBC 连接串而不是数据库名。这条连接串的每段都有意义:Driver指定微软提供的 ODBC 驱动;Server里的127.0.0.1是本机,1433是 SQL Server 默认端口;Trusted_Connection=Yes表示用 Windows 当前用户身份登录,免去在代码里写死Uid和Pwd,如果要用 SQL Server 账号,就把这一项换成Uid=sa;Pwd=密码;;Encrypt=no和TrustServerCertificate=yes是专治新版 ODBC 驱动 SSL 报错的,第 5 章会展开。
改完后直接构建运行。Qt Creator 里选 MSVC 套件,点击运行,如果编译通过但界面弹出连接失败,先别急着改代码,回到第 3.2 步的 ODBC 数据源管理器测试,两边报错信息对比着看到底是谁的问题。命令行方式也一样:先qmake SQL.pro生成 Makefile,再nmake编译,出错时看第一行错误,Qt 的编译错误提示往往到最后那几行才是最根本的原因。
4. 核心代码走读:增删改查与表单联动是怎么串起来的
这个工程的业务逻辑不算复杂,核心就是把“主窗口刷新列表”和“对话框增删改”串起来。我挑三个最值得看的点展开:主窗口初始化、add 系列对话框提交、areyousure 二次确认。
4.1 主窗口初始化:从连接数据库到列表刷新
主窗口构造函数里通常这么写:初始化数据库、创建 QStandardItemModel 作为表格模型、调用加载函数填充 View。下面是常见写法,你可以对照源码找同样逻辑:
// MainWindow 构造里调用 void MainWindow::initWindows() { if (!initDatabase()) { QMessageBox::critical(this, "错误", "数据库连接失败,请检查 SQL Server 配置"); return; } model = new QStandardItemModel(this); ui->tableView->setModel(model); loadStudentList(); } void MainWindow::loadStudentList() { QSqlQuery query; query.exec("SELECT stu_no, stu_name, gender, birthday " "FROM Student ORDER BY stu_no"); model->clear(); model->setHorizontalHeaderLabels( QStringList() << "学号" << "姓名" << "性别" << "出生日期"); while (query.next()) { QString stuNo = query.value(0).toString(); QString stuName = query.value(1).toString(); QString gender = query.value(2).toString(); QString birthday = query.value(3).toDate().toString("yyyy-MM-dd"); QList<QStandardItem*> row; row << new QStandardItem(stuNo) << new QStandardItem(stuName) << new QStandardItem(gender) << new QStandardItem(birthday); model->appendRow(row); } }QSqlQuery默认使用当前线程里最后一个打开的数据库连接,所以不用每次手动指定。query.value(0)按列索引取值,顺序和 SELECT 里的字段顺序一致;生日是 DATE 类型,Qt 读到的是 QVariant,用toDate().toString("yyyy-MM-dd")保证显示格式,不要直接 toString,否则可能带出T和时区尾巴。model->clear()只清数据,表头需要重新 setHorizontalHeaderLabels,这是新手最容易漏的,漏了会导致第一次加载有表头,第二次刷新后表头消失。
4.2 addbody 与 addfamily 这类录入对话框的提交套路
add 系列对话框通用套路是:界面用 .ui 拖好 QLineEdit、QSpinBox、QDateEdit,确定按钮的槽函数里拼接 SQL 执行。我以 addbody 体检录入为例,用带参数绑定的写法,这是比你直接拼 QString 稳妥得多的方式:
void AddBodyDialog::onSave() { QSqlQuery query; query.prepare("INSERT INTO BodyInfo " "(stu_no, height, weight, vision, check_date) " "VALUES (?, ?, ?, ?, ?)"); query.addBindValue(ui->stuNoEdit->text().trimmed()); query.addBindValue(ui->heightSpin->value()); query.addBindValue(ui->weightSpin->value()); query.addBindValue(ui->visionEdit->text().trimmed()); query.addBindValue(QDate::currentDate().toString("yyyy-MM-dd")); if (!query.exec()) { QMessageBox::warning(this, "保存失败", query.lastError().text()); return; } this->accept(); }prepare加addBindValue的本质是把数据通过 ODBC 参数接口传给 SQL Server,而不是把字符串拼进 SQL 再发给服务端。这样有两个好处:一是用户在国家或姓名框里输入单引号、百分号时不会破坏 SQL 语法,也就是防注入;二是中文内容经过正确编码传输,乱码率大幅降低。高度和体重的 QSpinBox 返回的是 int,金额字段如果是 QDoubleSpinBox,注意小数位数设置,SQL Server 的 DECIMAL(10,2) 对应两位小数。如果要做编辑,把 INSERT 换成 UPDATE,并且 WHERE 后面加主键条件,比如WHERE body_id = ?,再绑定当前选中行的 body_id。
对话框点击保存后调用accept(),主窗口里通过if (dlg.exec() == QDialog::Accepted) loadStudentList();来判断到底刷新不刷新列表。这里有个容易踩的小坑:exec()是阻塞的,但后面的代码在对话框关闭后才会执行,如果你忘了判断返回值,用户点了“取消”也会刷新列表,虽然结果没错,但会给用户造成“我没保存啊,怎么列表变了”的错觉。
4.3 areyousure 确认框:把删除操作做成强制二次确认
删除数据是不可逆操作,所以工程里单独做了一个 areyousure 对话框,而不是直接用 QMessageBox 的默认按钮。加这一层的目的不是UI好看,而是防止鼠标误点:列表里选中一行,按删除键本来是很顺手的事,如果没有确认框,一行数据就没了,而且 SQL Server 里外键关联的 BodyInfo、Payment 记录可能也被级联删掉。
调用方式通用,删除任何一张表都用同一个确认框:
Areyousure dlg(this); if (dlg.exec() != QDialog::Accepted) { return; // 用户选了取消,什么都不做 } QSqlQuery query; query.prepare("DELETE FROM Student WHERE stu_no = ?"); query.addBindValue(currentStuNo); if (query.exec()) { loadStudentList(); }我这里补充一个源码里很可能没有但实战非常需要的点:多表关联删除。Student 主表被 BodyInfo、Payment 通过外键引用,仅删除主表会在外键检查时报错。正确顺序是先删子表再删主表,或者把几个 DELETE 包进事务,防止删了一半程序崩溃留下孤儿数据:
BEGIN TRANSACTION; DELETE FROM BodyInfo WHERE stu_no = '2023001'; DELETE FROM Payment WHERE stu_no = '2023001'; DELETE FROM Student WHERE stu_no = '2023001'; COMMIT;确认框里文案最好带上当前操作对象的标识,比如“确认删除学号为 2023001 的学生信息吗?”,不然用户连删的是谁都看不清。
5. 避坑排查:ODBC 连接失败、Qt 版本不匹配、中文乱码的 5 条血泪记录
这一章是全篇含金量最高的部分,每一条都是我实际跑课程设计类项目踩过的坑。学生管理系统本身不难写,难的是环境组合千奇百怪,数据库报错信息还经常误导人。
5.1 现象:QSqlDatabase: QODBC driver not loaded
程序编译正常,运行时打开数据库报QSqlDatabase: QODBC driver not loaded,open 失败。
原因分两种:一是安装 Qt 时没有勾选 Qt SQL 驱动模块,plugins/sqldrivers 目录下根本没有 qsqlodbc.dll;二是发布时只拷了 exe 和 dll,忘了把 qsqlodbc.dll 放到项目运行目录的 sqldrivers 子文件夹下。
解决:开发环境下先检查 Qt 安装目录D:\Qt\5.15.2\msvc2017_64\plugins\sqldrivers,看有没有 qsqlodbc.dll,没有就重装 Qt 或在 MaintenanceTool 里勾选 Database Drivers 的 ODBC 子项。发布到别的机器时,用windeployqt.exe 你的程序.exe自动拷贝依赖,它会连插件目录一起带过去;如果手工拷贝,必须把整个 sqldrivers 目录放到 exe 同级,不能少。我见过最诡异的情况是 qsqlodbc.dll 在,但依赖的 Qt5Sql.dll 是 MinGW 版本的,和主程序 MSVC 编译的不匹配,运行时一样报驱动未加载。
5.2 现象:错误 08001,证书链由不受信任的颁发机构颁发
用 ODBC Driver 17/18 连接时报[Microsoft][ODBC Driver 17 for SQL Server]SSL 提供程序: 证书链是由不受信任的颁发机构颁发的,或者客户端无法建立连接。网上搜这个错能搜出一堆长篇大论,但多半是绕圈子。
原因:新版微软 ODBC 驱动默认把Encrypt(加密)设为 yes,也就是要求对连接做 SSL 加密,同时验证服务器证书。而默认安装的 SQL Server 用的是自签名证书,不在系统信任列表里,于是握手阶段直接失败。
解决:连接串末尾加两个参数,第 3 章代码里我已经写了,这里再强调一次:
db.setDatabaseName( "Driver={ODBC Driver 17 for SQL Server};" "Server=127.0.0.1,1433;" "Database=StudentDB;" "Trusted_Connection=Yes;" "Encrypt=no;" "TrustServerCertificate=yes;");Encrypt=no关闭强制加密,TrustServerCertificate=yes表示即使证书不可信也继续连接。这两个参数是配套的,只加一个不保险。如果你用的 ODBC Driver 13 或更老版本,这条报错通常不会出现,所以也有人用“回退旧版本驱动”来解决,但在新机器上装旧版驱动,又容易触发第 5.5 条的问题。
5.3 现象:fatal: cannot mix incompatible Qt library (version ex50601) with this library
编译时直接报fatal: cannot mix incompatible Qt library (version ex50601) with this librar,有时候前面还跟着unknown module(s) in qt: webenginewidgets。
原因:Qt 版本和编译器套件混用。最典型的是解压了一个用 Qt 5.6.1 编译的工程,然后用 Qt 5.15.2 的 MSVC 套件直接打开,.pro 文件里的模块名和头文件路径对不上;还有一种是 MinGW 编译的工程被强制切到 MSVC 套件,或者反过来,Qt 内部宏QT_VERSION_STR检查不通过。
解决:确认工程是哪个版本写的,README 或 SQL.pro.user 文件里一般留有痕迹。如果工程是 MinGW 的,就用 MinGW 套件打开,并且同一套件下清理重新 qmake;如果工程是 MSVC 的,就统一用 MSVC 套件。最彻底的办法是把 build 目录整个删掉,重新 qmake 再全量编译,因为增量编译残留的 .o 文件里带着旧宏定义,比全新编译更容易触发版本错乱。
5.4 现象:写入 SQL Server 的中文全部变成问号
界面上显示正常,但从 Qt 写入数据库后,SSMS 里查出来是???,反过来在 SSMS 里手工插入中文,Qt 界面读到也是乱码。
原因:三层编码没对齐。第一层是源文件编码,如果 .cpp 文件本身是 GBK 保存的,Qt 5.15 默认按 UTF-8 解析字符串字面量,中文 QT 字符串在编译期就已经乱了;第二层是数据库排序规则,默认安装的 SQL Server 可能是SQL_Latin1_General_CP1_CI_AS,存不了中文;第三层是 ODBC 传输编码,老版本驱动对中文支持参差不齐。
解决:源文件统一 UTF-8,Qt Creator 右下角有个编码菜单可以批量转换;建库时强制用COLLATE Chinese_PRC_CI_AS,第 2.2 节脚本里已经写了;插入和查询都走参数绑定,不要拼字符串。如果你是在老系统上排查,还可以在连接串里加CharacterSet=UTF8,但新驱动一般不需要。判断是哪层的问题有个笨办法:在 Qt 里qDebug() << query.lastError(),如果错误信息里中文本身是好的,数据库里却是问号,那问题在数据库排序规则;如果错误信息里中文就是乱码,那问题在源文件编码。
5.5 现象:SSMS 能连,Qt 里连不上,报驱动版本不兼容
服务器是老机器,SQL Server 2008/2012,客户端机器却装了 ODBC Driver 17/18,Qt 里报[08001]或者直接提示找不到数据源。
原因:新版 ODBC 驱动在握手协议上和新版 SQL Server 匹配良好,但对 2008/2012 这类老实例的支持不够平滑,尤其是默认加密行为变化后,老实例不认识新驱动的加密协商请求。你可以在 ODBC 数据源管理器里测连接,往往会发现“测试失败”,但 SSMS 用旧协议却能连上,这就是黑匣子一样的体验。
解决:这种情况别硬抗,连接串里的Driver改回{SQL Server}(注意是旧版驱动名)或者安装SQL Server Native Client 11.0后改用{SQL Server Native Client 11.0}。代价是 SSL 加密会失效,但在局域网课设环境里无所谓。反过来,如果你连的是 SQL Server 2019/2022,那优先用 ODBC Driver 17/18,别提老驱动,老驱动对新版 TLS 1.2 以上协议反而支持不好。一句话:驱动版本跟随服务端版本走,不要一路装最新。
6. 进阶技巧:把连接串拆出来、加登录日志、让数据库可以一键备份
到这里工程已经能跑,增删改查也通透了。但课设交付和实际部署之间还有一步距离:谁都不想每次换机器就重新编译一次代码。下面这三个小改造花不了半小时,却能大幅提升代码的可用性。
第一,连接串外置到 config.ini。把第 3.3 节的连接串从.cpp里挪到配置文件中,程序启动时动态读取,这样部署到别的机器只需要改 ini 文件里的服务器地址和数据库名,不用动一行代码。读取方式用 QSettings:
QSettings settings("config.ini", QSettings::IniFormat); QString server = settings.value("db/server", "127.0.0.1").toString(); QString dbName = settings.value("db/name", "StudentDB").toString(); QString connect = QString( "Driver={ODBC Driver 17 for SQL Server};" "Server=%1;Database=%2;Trusted_Connection=Yes;Encrypt=no;" "TrustServerCertificate=yes;") .arg(server, dbName);第二,给登录记录加日志表。按用户习惯补一张登录日志:
CREATE TABLE LoginLog ( log_id INT IDENTITY(1,1) PRIMARY KEY, user_name NVARCHAR(50), login_time DATETIME DEFAULT GETDATE() );登录成功后在 Qt 里执行一条 INSERT 即可。别小看这张表,课设答辩时老师问“你系统有没有操作留痕”,这就是现成的回答素材。
第三,用 QProcess 调用 sqlcmd 做一键备份。把数据库备份命令放在“备份”按钮的槽函数里,省去手动开 SSMS 的麻烦:
QProcess::startDetached("sqlcmd", QStringList() << "-S" << "127.0.0.1" << "-E" << "-Q" << "BACKUP DATABASE StudentDB TO DISK='D:/bak/StudentDB.bak'");参数解释:-S指定服务器,-E表示使用 Windows 身份验证,-Q后面跟要执行的 SQL 语句。这一招在答辩演示前跑一遍,就算前面操作删错了数据,也能立刻还原,相当于给课设买了后悔药。
有一回我把连接串直接写在构造函数里,换了一台只改了实例名的机器,愣是查了一晚上,最后才发现问题出在驱动版本和Encrypt参数上,而不是代码逻辑。从那以后,我每次拿到别人的 Qt 工程,都会先看 README 和 .pro,再把数据库连接拆出来放 config.ini,最后强制走一遍“新建-编辑-删除-查询”全流程才敢往下改。这套习惯帮我少踩了至少五个坑,希望帮到你。
本文还有配套的精品资源,点击获取