Qt SQL 把连接、查询和表格模型分开:QSqlDatabase 表示连接,QSqlQuery 执行语句,QSqlTableModel 为视图提供表数据。示例先用 SQLite 的内存数据库演示完整流程,再说明切换到 MySQL 时真正需要改变的配置。
原片段中的 QMYSYQL、setPassward、dp.open()、QMessgateBox、qDebug 少括号,以及中文逗号和分号,都不是可编译的 Qt 写法。修正拼写之后,还需要解决 SQL 参数绑定、连接身份和对象寿命。
连接名称、数据库名称与驱动
跳转到“连接名称、数据库名称与驱动”| 项目 | SQLite 示例 | MySQL 对应 |
|---|---|---|
| 驱动标识 | "QSQLITE" | "QMYSQL" |
| Qt 连接名称 | 如 "example-sql",用来区分本进程中的连接 | 同样需要唯一名称 |
setDatabaseName | 文件路径,或 ":memory:" | 服务端数据库名称 |
| 服务地址 | 不需要网络服务器 | setHostName、需要时 setPort |
| 身份认证 | 不使用服务器用户名和密码 | setUserName、setPassword 或带凭据的 open 重载 |
连接名称不是数据库名称。反复不带名称调用 addDatabase 会替换默认连接;多连接程序使用唯一名称,并明确构造 QSqlQuery(db) 或 QSqlTableModel(parent, db)。QSqlDatabase 的复制值仍指向同一连接,不能把复制当成新增独立连接。QSqlDatabase
MySQL 的主机、端口、数据库、用户名及密码由应用配置提供。不要把原例的 root/root 作为通用默认值。QSqlDatabase::drivers() 和 isDriverAvailable("QMYSQL") 可帮助检查插件,但驱动存在不等于连接一定成功:插件依赖库、认证、服务地址与数据库权限仍需分别排查。Qt SQL 驱动
将数据作为参数绑定
跳转到“将数据作为参数绑定”姓名可能包含单引号,例如 O'Brien。用 QString::arg() 将它拼入 SQL 字符串,会把数据混进语句语法。改用 prepare() 加 bindValue(),并检查准备与执行结果。
占位符用于值,不能把任意表名、列名或排序表达式当作值绑定。动态标识符应从明确允许的集合中选择。不同驱动可能将语句检查推迟到 exec(),因此 prepare() 成功也不能省略执行结果检查。QSqlQuery
完整例子:两张表、绑定查询与只读表格
跳转到“完整例子:两张表、绑定查询与只读表格”程序使用 Qt 6、C++17,链接 Qt6::Widgets 与 Qt6::Sql。默认执行检查后退出;加 --show 才显示表格并进入界面事件循环。以 Debug 配置保留断言。
保留两份原笔记的用途:mytable 中有自增 id 和 name,插入 John 并查询;student 中有 id/name/birth,交给 QTableView 展示。额外加入单引号值、事务回滚和错误 SQL 的检查。
#include <QAbstractItemView>#include <QApplication>#include <QDebug>#include <QSqlDatabase>#include <QSqlError>#include <QSqlQuery>#include <QSqlTableModel>#include <QStringList>#include <QTableView>#include <QVariant>#include <cassert>#include <stdexcept>
void require(bool success, const QString& message) { if (!success) throw std::runtime_error(message.toUtf8().constData());}
void execute(QSqlQuery& query, const QString& sql) { const bool success = query.exec(sql); require(success, query.lastError().text());}
void prepare(QSqlQuery& query, const QString& sql) { const bool success = query.prepare(sql); require(success, query.lastError().text());}
void executePrepared(QSqlQuery& query) { const bool success = query.exec(); require(success, query.lastError().text());}
void prepareData(const QSqlDatabase& db) { QSqlQuery query(db); execute(query, "CREATE TABLE mytable (" "id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)"); execute(query, "CREATE TABLE student (" "id INTEGER PRIMARY KEY, name TEXT NOT NULL, birth TEXT NOT NULL)");
prepare(query, "INSERT INTO mytable(name) VALUES(:name)"); for (const QString& name : {QStringLiteral("John"), QStringLiteral("O'Brien")}) { query.bindValue(":name", name); executePrepared(query); }
prepare(query, "INSERT INTO student(id, name, birth) VALUES(:id, :name, :birth)"); query.bindValue(":id", 7); query.bindValue(":name", "John"); query.bindValue(":birth", "2000-01-01"); executePrepared(query);
execute(query, "SELECT id, name FROM mytable ORDER BY id"); QStringList names; while (query.next()) { assert(query.value(0).toInt() > 0); names.append(query.value(1).toString()); qDebug() << "ID:" << query.value(0).toInt() << "Name:" << names.last(); } require(!query.lastError().isValid(), query.lastError().text()); assert((names == QStringList{"John", "O'Brien"}));}
void checkRollback(QSqlDatabase& db) { const bool started = db.transaction(); require(started, db.lastError().text()); try { QSqlQuery query(db); execute(query, "INSERT INTO student(id, name, birth) " "VALUES(8, 'Temporary', '2001-01-01')"); } catch (...) { if (!db.rollback()) qWarning() << "Rollback failed:" << db.lastError().text(); throw; } const bool rolledBack = db.rollback(); require(rolledBack, db.lastError().text());
QSqlQuery check(db); execute(check, "SELECT COUNT(*) FROM student"); require(check.next(), QStringLiteral("Missing count row")); assert(check.value(0).toInt() == 1); check.finish(); const bool invalidSucceeded = check.exec("SELECT missing_column FROM student"); assert(!invalidSucceeded && check.lastError().isValid());}
int showOrCheckTable(QApplication& app, const QSqlDatabase& db, bool show) { QSqlTableModel model(nullptr, db); model.setTable("student"); model.setEditStrategy(QSqlTableModel::OnManualSubmit); const bool selected = model.select(); require(selected, model.lastError().text()); assert(model.rowCount() == 1 && model.columnCount() == 3); assert(model.data(model.index(0, 1)).toString() == "John");
QTableView view; view.setModel(&model); view.setEditTriggers(QAbstractItemView::NoEditTriggers); view.setWindowTitle("Student table"); if (!show) return 0; view.show(); return app.exec();} // view 与 model 在连接关闭之前销毁
int main(int argc, char* argv[]) { QApplication app(argc, argv); const QString connection = QStringLiteral("example-sql"); int result = 1; { QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", connection); db.setDatabaseName(":memory:"); try { require(db.isValid(), QStringLiteral("QSQLITE driver is unavailable")); const bool opened = db.open(); require(opened, db.lastError().text()); prepareData(db); checkRollback(db); result = showOrCheckTable(app, db, app.arguments().contains("--show")); } catch (const std::exception& error) { qCritical().noquote() << QString::fromUtf8(error.what()); } db.close(); } // db 的句柄也已经销毁 QSqlDatabase::removeDatabase(connection); assert(!QSqlDatabase::contains(connection)); return result;}这不是通用的跨数据库建表脚本:示例中的自增语法属于 SQLite。使用 MySQL 时,应按自己的表结构与服务端 SQL 方言调整建表语句,不能仅换驱动名就假定全部 SQL 不变。上述程序没有连接 MySQL 服务器。
QSqlTableModel::select() 加载表数据。只读界面关闭编辑触发器;若需要编辑,再明确选择 OnFieldChange、OnRowChange 或 OnManualSubmit,并处理提交与回滚错误。模型可能分批读取较大结果集,因此不要从本例的一行表推导所有 rowCount() 都立即表示服务器总行数。QSqlTableModel
生命周期与退出条件
跳转到“生命周期与退出条件”先销毁依赖连接的查询和模型,再关闭连接;所有 QSqlDatabase 句柄离开作用域后,才调用 removeDatabase。close() 影响该连接的其他副本,也不能替代“所有使用者都已结束”的条件。
一次性同步程序做完查询后可以正常退出。原示例在打开失败、建表失败和查询结束后都调用 a.exec(),却没有安排任何后续事件来结束程序,会让它继续等待。真正的界面或异步程序才需要相应事件循环。
连接应在所属线程内使用。工作线程需要独立连接与清理流程;不能把主线程创建的连接复制一份就当作线程隔离。本文也没有验证多线程数据库操作或服务端并发事务行为。