3、Qt SQL API核心类:QSqlDatabase、QSqlQuery、QSqlRecord、QSqlField、QSqlError详解

好,咱们直接切入正题。上一章我们把Qt SQL的架构捋了一遍,知道了它分驱动层、API层和用户界面层。这一章,咱们就深入API层的几个核心类。说白了,你写任何数据库程序,天天打交道的就这几个家伙:QSqlDatabase、QSqlQuery、QSqlRecord、QSqlField 和 QSqlError。

我个人习惯,学一个新东西,先搞清楚它「是什么」和「怎么用」。咱们一个一个来。

3.1 QSqlDatabase:你的数据库连接管家

QSqlDatabase 是啥?它就是你和数据库之间的那条「连接线」。你想想看,你要跟MySQL、SQLite或者PostgreSQL说话,总得先打个招呼、建立个会话吧?QSqlDatabase 干的就是这个活儿。

它负责管理连接参数:数据库类型、主机名、端口、用户名、密码、数据库名。嗯,这里要注意,QSqlDatabase 本身不存储数据,它只是一个连接句柄。

核心要点: 一个 QSqlDatabase 实例代表一个数据库连接。你可以创建多个连接,但每个连接必须有唯一的连接名。

看个最简单的例子,连接SQLite:

// 创建一个默认连接
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("mydb.sqlite");

if (!db.open()) {
    qDebug() << "数据库打开失败:" << db.lastError().text();
    return;
}
// 连接成功,可以干活了

我在项目中遇到过一个问题:多个线程同时操作同一个数据库连接,结果各种崩溃。后来才明白,QSqlDatabase 不是线程安全的。每个线程最好用自己的连接,或者加锁保护。这是个坑,我踩过。

3.2 QSqlQuery:执行SQL的利刃

连接建好了,接下来就是执行SQL语句。QSqlQuery 就是干这个的。它支持所有标准的SQL操作:SELECT、INSERT、UPDATE、DELETE,以及存储过程调用。

我个人习惯把 QSqlQuery 比作「数据库的嘴巴」——你告诉它要说什么(SQL语句),它帮你传达给数据库,再把结果带回来。

来看一个查询的例子:

QSqlQuery query;
query.exec("SELECT id, name, age FROM users WHERE age > 18");

while (query.next()) {
    int id = query.value(0).toInt();
    QString name = query.value(1).toString();
    int age = query.value(2).toInt();
    qDebug() << id << name << age;
}

这里有个细节:query.next() 每次移动一行,返回 true 表示还有数据。第一次调用 next() 会定位到第一行。如果你想知道有多少行,可以用 query.size(),但注意,不是所有数据库驱动都支持这个。

小技巧: 用 query.value("column_name") 按列名取值,代码可读性更好。我一般不用索引,除非列名特别长。

说到SQL注入,这可是个大问题。千万别用字符串拼接来构造SQL!用 bindValue 或者 prepare 才是正道:

QSqlQuery query;
query.prepare("INSERT INTO users (name, age) VALUES (?, ?)");
query.addBindValue("张三");
query.addBindValue(25);
query.exec();

我曾经接手过一个项目,里面全是字符串拼接的SQL,看得我心惊肉跳。后来花了一周时间全部改成参数绑定。安全第一,没得商量。

3.3 QSqlRecord:结果集的元数据

当你执行完一个查询,QSqlQuery 里其实藏着两个东西:数据本身,以及数据的「描述信息」。这个描述信息就是 QSqlRecord。

QSqlRecord 告诉你:结果集有多少列?每列叫什么名字?每列是什么类型?说白了,它就是结果集的「目录」。

怎么拿到它?很简单:

QSqlQuery query("SELECT * FROM users");
QSqlRecord record = query.record();

// 遍历所有列
for (int i = 0; i < record.count(); ++i) {
    QString fieldName = record.fieldName(i);
    QVariant::Type fieldType = record.field(i).type();
    qDebug() << "列名:" << fieldName << "类型:" << fieldType;
}

嗯,这里要注意,record() 返回的是当前查询的元数据,不是数据库表的元数据。如果你想知道表结构,得用 QSqlDatabase::record("table_name")。

我在做动态表单生成器的时候,就是靠 QSqlRecord 来获取字段信息,然后自动生成输入控件的。省了不少事。

3.4 QSqlField:单个字段的详细信息

QSqlField 是比 QSqlRecord 更细粒度的东西。它描述的是「一个字段」的属性:字段名、字段类型、是否允许为空、默认值、最大长度等等。

你可以通过 QSqlRecord::field() 拿到它:

QSqlRecord record = query.record();
QSqlField field = record.field("age");

qDebug() << "字段名:" << field.name();
qDebug() << "类型:" << field.typeID();  // 数据库原生类型ID
qDebug() << "是否可为空:" << field.isNull();
qDebug() << "默认值:" << field.defaultValue();
qDebug() << "最大长度:" << field.length();

说实话,QSqlField 在日常开发中用得不多。但如果你要写一个数据库管理工具,或者做数据校验,它就派上大用场了。比如,你可以根据字段类型自动选择输入控件:字符串用 QLineEdit,整数用 QSpinBox,日期用 QDateEdit。

注意: QSqlField 的 type() 返回的是 QVariant::Type,不是数据库原生类型。要获取原生类型,用 typeID()。但 typeID() 的返回值依赖于具体的数据库驱动,可移植性较差。

3.5 QSqlError:错误处理的救星

写数据库程序,不出错是不可能的。连接失败、SQL语法错误、约束违反……各种问题防不胜防。QSqlError 就是 Qt 给咱们准备的错误信息容器。

它包含三个关键信息:

  • driverText():数据库驱动返回的错误信息
  • databaseText():数据库服务器返回的错误信息
  • nativeErrorCode():数据库原生的错误码

用法很简单:

QSqlQuery query;
if (!query.exec("SELECT * FROM non_existent_table")) {
    QSqlError err = query.lastError();
    qDebug() << "驱动错误:" << err.driverText();
    qDebug() << "数据库错误:" << err.databaseText();
    qDebug() << "错误码:" << err.nativeErrorCode();
}

我个人习惯,在关键操作后都检查一下错误。别偷懒,不然出了问题排查起来很痛苦。我曾经因为没检查 QSqlDatabase::open() 的返回值,导致程序在用户机器上静默失败,用户还以为程序没反应。后来加了个错误弹窗,问题立刻暴露了。

另外,QSqlError::type() 可以告诉你错误的严重级别:

类型 含义
NoError 没有错误
ConnectionError 连接错误
StatementError SQL语句错误
TransactionError 事务错误
UnknownError 未知错误

3.6 它们之间的关系

说了这么多,这几个类到底怎么配合?我画了张图,一看就明白:

Qt SQL API 核心类关系图 QSqlDatabase 管理数据库连接 创建 QSqlQuery 执行SQL、遍历结果 record() QSqlRecord 结果集元数据 field() QSqlField QSqlError 错误信息 lastError()

这张图很清楚地展示了:QSqlDatabase 创建 QSqlQuery,QSqlQuery 执行后通过 record() 拿到 QSqlRecord,再通过 field() 获取 QSqlField。而 QSqlError 则贯穿始终,任何操作出错都可以通过 lastError() 获取。

3.7 实战小贴士

最后,分享几个我实际项目中的经验:

  • 连接命名:多连接时,给每个连接起个有意义的名字,比如 "main_connection"、"backup_connection"。别用默认的空字符串,不然排查起来很头疼。
  • 查询重用:同一个SQL反复执行时,用 prepare() + bindValue() 的方式,效率比 exec() 高很多。数据库会缓存执行计划。
  • 错误日志:把 QSqlError 的详细信息记录到日志文件里。用户反馈问题时,日志就是你的救命稻草。
  • 字段索引:用 record.indexOf("column_name") 获取字段索引,比硬编码索引号更安全。表结构变了,代码也不会立刻崩。

好了,这一章的内容就到这儿。这几个类虽然基础,但用好它们,你的Qt数据库程序就稳了一大半。下一章咱们聊聊事务处理和高级查询技巧,到时候见。

总结: QSqlDatabase 管连接,QSqlQuery 管执行,QSqlRecord 管元数据,QSqlField 管字段细节,QSqlError 管错误处理。五者配合,天下无敌。

可可精品资料库