一、MySQL 入门
核心概念
在 QT 中操作数据库,主要使用两个模块:
-
QSqlDatabase
:代表一个数据库连接。 -
QSqlQuery
:用于执行 SQL 语句(如SELECT
,INSERT
,UPDATE
,DELETE
)并处理结果。
环境准备
在编写代码之前,你需要确保系统已具备以下条件:
1. 安装 MySQL
-
从 MySQL 官网 下载并安装 MySQL 服务器和客户端。
-
记住你设置的 root 密码,或者创建一个用于测试的新数据库用户。
2. 安装 QT
-
确保你的 QT 安装包含了
Qt SQL
模块。在安装 QT 时,通常默认会包含。
3. 安装 MySQL 驱动(最关键的一步)
QT 默认可能不包含 MySQL 的驱动插件,你需要自己编译或确保它存在。
检查现有驱动:
你可以写一个简单的程序来查看当前可用的数据库驱动。
cpp
#include <QCoreApplication>
#include <QSqlDatabase>
#include <QDebug>int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "Available drivers:";QStringList drivers = QSqlDatabase::drivers();foreach(QString driver, drivers)qDebug() << driver;return 0;
}
如果输出中包含 "QMYSQL"
或 "QMYSQL3"
,说明驱动已安装。如果没有,你需要手动编译。
如何编译 MySQL 驱动?
-
找到 QT 的源码目录:确保你有 QT 的源代码。路径通常像
~/Qt/5.15.2/Src
(5.15.2 是版本号)。 -
找到驱动代码:进入
~/Qt/5.15.2/Src/qtbase/src/plugins/sqldrivers/mysql
。 -
编译:
-
Windows: 使用 Qt Creator 打开该目录下的
.pro
工程文件进行编译。你需要确保在项目的构建设置中指定 MySQL 的include
和lib
目录。 -
Linux (Ubuntu/Debian):
bash
# 安装 MySQL 开发文件 sudo apt-get install libmysqlclient-dev# 进入驱动目录 cd ~/Qt/5.15.2/Src/qtbase/src/plugins/sqldrivers# 使用 qmake 生成 Makefile,指定 MySQL 的路径(如果不在标准路径) qmake -- MYSQL_PREFIX=/usr/include/mysql # 或者你的 MySQL 安装路径 make make install # 将编译好的插件复制到 QT 的插件目录
-
macOS (使用 Homebrew):
bash
# 安装 MySQL brew install mysql# 进入驱动目录 cd ~/Qt/5.15.2/Src/qtbase/src/plugins/sqldrivers# 使用 qmake qmake "INCLUDEPATH+=/usr/local/opt/mysql/include" "LIBS+=-L/usr/local/opt/mysql/lib -lmysqlclient" mysql.pro make make install
-
编译成功后,生成的 libqsqlmysql.so
(Linux), qsqlmysql.dll
(Windows),或 libqsqlmysql.dylib
(macOS) 文件会被复制到 QT 的插件目录(如 Qt/5.15.2/gcc_64/plugins/sqldrivers
)。确保你的程序运行时能找到这个插件。
编写代码
假设我们已经有一个名为 test_db
的数据库,其中有一张表 users
:
sql
CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(100) NOT NULL,email VARCHAR(100) NOT NULL
);
步骤 1:项目配置 (.pro 文件)
在你的 QT 项目文件 (.pro
) 中,添加 sql
模块。
pro
QT += core gui sql # 添加 sql 模块
步骤 2:C++ 代码实现
以下是完整的示例代码,包含连接数据库、插入、查询、更新和删除操作。
cpp
#include <QCoreApplication>
#include <QtSql> // 包含所有 SQL 相关的头文件
#include <QDebug>int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 1. 建立并打开数据库连接QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL"); // 指定数据库类型db.setHostName("localhost"); // 数据库服务器 IP,本地则为 localhostdb.setPort(3306); // 端口号,默认 3306db.setDatabaseName("test_db"); // 要连接的数据库名db.setUserName("root"); // 数据库用户名db.setPassword("your_password"); // 数据库密码if (!db.open()) {qDebug() << "Failed to connect to database:" << db.lastError().text();return -1;} else {qDebug() << "Database connected successfully!";}// 2. 执行 SQL 语句 (使用 QSqlQuery)QSqlQuery query;// --- INSERT 示例 ---query.prepare("INSERT INTO users (name, email) VALUES (:name, :email)");query.bindValue(":name", "Alice");query.bindValue(":email", "alice@example.com");if (!query.exec()) {qDebug() << "Insert failed:" << query.lastError().text();} else {qDebug() << "Insert successful!";}// --- SELECT 示例 ---if (!query.exec("SELECT id, name, email FROM users")) {qDebug() << "Select failed:" << query.lastError().text();} else {qDebug() << "\nQuery results:";while (query.next()) { // 遍历结果集int id = query.value(0).toInt(); // 通过索引获取字段QString name = query.value("name").toString(); // 通过字段名获取QString email = query.value(2).toString(); // 通过索引获取字段qDebug() << "ID:" << id << "Name:" << name << "Email:" << email;}}// --- UPDATE 示例 ---query.prepare("UPDATE users SET email = ? WHERE name = ?");query.addBindValue("new_alice@example.com"); // 绑定第一个问号的值query.addBindValue("Alice"); // 绑定第二个问号的值if (!query.exec()) {qDebug() << "Update failed:" << query.lastError().text();}// --- DELETE 示例 ---query.prepare("DELETE FROM users WHERE name = :name");query.bindValue(":name", "Alice");if (!query.exec()) {qDebug() << "Delete failed:" << query.lastError().text();}// 3. 关闭数据库连接 (可选,程序结束时会自动关闭)db.close();return 0; // return a.exec(); 如果是 GUI 应用
}
代码详解
-
QSqlDatabase::addDatabase(“QMYSQL”)
: 创建一个 MySQL 类型的数据库连接。"QMYSQL"
是驱动的名称。 -
setHostName(), setDatabaseName(), ...
: 设置连接数据库所需的参数。 -
db.open()
: 尝试连接数据库。如果失败,可以通过db.lastError()
获取错误信息。 -
QSqlQuery
: 用于执行 SQL 语句的核心类。 -
query.prepare()
+query.bindValue()
: 推荐使用 的预处理语句方式。它可以防止 SQL 注入攻击,并且更高效。:name
是命名占位符,?
是位置占位符。 -
query.exec()
: 执行 SQL 语句。对于SELECT
语句,它会返回一个结果集。 -
query.next()
: 遍历结果集中的每一条记录。初始时,指针位于第一条记录之前。 -
query.value()
: 获取当前记录中某个字段的值。可以通过数字索引(从 0 开始)或字段名称的字符串来访问。
高级主题 - 模型/视图编程
QT 提供了更高级的 QSqlTableModel
和 QSqlQueryModel
,可以将数据库中的数据直接与 QTableView
等视图组件绑定,实现数据的自动显示和编辑。
示例:使用 QSqlTableModel
在表格视图中显示数据
cpp
#include <QApplication>
#include <QTableView>
#include <QSqlTableModel>
#include <QSqlDatabase>
#include <QSqlError>int main(int argc, char *argv[]) {QApplication app(argc, argv);// ... (数据库连接代码,同上) ...// 创建模型并设置表QSqlTableModel *model = new QSqlTableModel;model->setTable("users");model->setEditStrategy(QSqlTableModel::OnManualSubmit); // 设置编辑策略model->select(); // 从数据库获取数据// 创建视图并设置模型QTableView *view = new QTableView;view->setModel(model);view->show();return app.exec();
}
常见错误与调试
-
QSqlDatabase: QMYSQL driver not loaded
-
原因:MySQL 驱动未正确安装或部署。
-
解决:按照本指南第一部分的步骤编译驱动,并确保生成的驱动文件在程序的运行时可访问路径下(通常是程序所在目录的
sqldrivers
子文件夹,或者 QT 安装目录的plugins/sqldrivers
文件夹)。
-
-
Unknown database ‘test_db’
-
原因:MySQL 中不存在该数据库。
-
解决:先用 MySQL 客户端(如命令行或 Workbench)创建数据库。
-
-
Access denied for user ‘root’@‘localhost’
-
原因:用户名或密码错误,或者该用户没有从本地主机连接的权限。
-
解决:检查登录凭据,或在 MySQL 中为用户授权。
-
-
Can’t connect to MySQL server on ‘localhost’ (10061)
-
原因:MySQL 服务没有启动。
-
解决:去服务管理器中启动 MySQL 服务。
-
调试技巧:
-
始终检查
open()
和exec()
的返回值。 -
使用
lastError().text()
来获取人类可读的错误信息。这是你最好的朋友!
二、MySQL 数据类型
核心关系对应表
MySQL 数据类型 | 推荐 / 对应的 C++ 类型 | Qt 中常用的读取方法 (从 QSqlQuery 或 QSqlRecord ) | Qt 中常用的写入方法 (到 QSqlQuery 预处理语句) | 注意事项 |
---|---|---|---|---|
整数类型 | ||||
TINYINT | int / bool | .toInt() / .toBool() | :value 绑定 int / bool | TINYINT(1) 通常被视为布尔值。 |
SMALLINT | int | .toInt() | :value 绑定 int | |
MEDIUMINT | int | .toInt() | :value 绑定 int | |
INT / INTEGER | int | .toInt() | :value 绑定 int | |
BIGINT | qint64 | .toLongLong() | :value 绑定 qint64 | 确保编译器支持 64 位整数。 |
浮点数类型 | ||||
FLOAT | float | .toFloat() | :value 绑定 float | 可能存在精度问题。 |
DOUBLE / REAL | double | .toDouble() | :value 绑定 double | |
DECIMAL(M, D) | QString | .toString() | :value 绑定 QString | 最佳实践:为避免浮点精度损失,金额等精确数值建议用 QString 读取。也可以用 .toDouble() 但需谨慎。 |
字符串类型 | ||||
CHAR(N) | QString | .toString() | :value 绑定 QString | Qt 会自动处理编码转换(通常使用 UTF-8)。 |
VARCHAR(N) | QString | .toString() | :value 绑定 QString | |
TINYTEXT | QString | .toString() | :value 绑定 QString | |
TEXT | QString | .toString() | :value 绑定 QString | 对于非常大的文本,确保有足够的内存。 |
BLOB | QByteArray | .toByteArray() | :value 绑定 QByteArray | 用于存储二进制数据,如图片、文件等。 |
LONGTEXT | QString | .toString() | :value 绑定 QString | |
LONGBLOB | QByteArray | .toByteArray() | :value 绑定 QByteArray | |
日期时间类型 | ||||
DATE | QDate | .toDate() | :value 绑定 QDate | MySQL 的日期格式与 QDate 兼容。 |
TIME | QTime | .toTime() | :value 绑定 QTime | |
DATETIME | QDateTime | .toDateTime() | :value 绑定 QDateTime | 注意:MySQL 5.6.4+ 支持小数秒,但 QDateTime 不直接支持,读取时会被截断。 |
TIMESTAMP | QDateTime | .toDateTime() | :value 绑定 QDateTime | 存储的是 UTC 时间,读取时 Qt 会将其转换为本地时间。写入时,Qt 会将本地时间转换为 UTC 存储。行为需确认。 |
其他类型 | ||||
ENUM('val1', 'val2') | QString | .toString() | :value 绑定 QString | 读取其字符串值。 |
SET('val1', 'val2') | QString | .toString() | :value 绑定 QString | 读取逗号分隔的字符串集合。 |
BOOL / BOOLEAN | bool | .toBool() | :value 绑定 bool | 本质上是 TINYINT(1) 的别名。 |
JSON (MySQL 5.7.8+) | QString | .toString() | :value 绑定 QString | 在 Qt 中作为字符串处理,解析可使用 QJsonDocument 。 |
如何使用:代码示例
1. 读取数据 (SELECT)
cpp
QSqlQuery query;
query.exec("SELECT id, name, salary, birth_date FROM employees");while (query.next()) {// 通过字段索引读取 (从0开始)int id = query.value(0).toInt(); // 通过字段名读取 (更安全可靠)QString name = query.value("name").toString();double salary = query.value("salary").toDouble(); // 注意DECIMAL的精度问题QDate birthDate = query.value("birth_date").toDate();qDebug() << "ID:" << id << "Name:" << name << "Salary:" << salary << "Birth Date:" << birthDate.toString(Qt::ISODate);
}
2. 写入数据 (INSERT / UPDATE) - 使用预处理语句(推荐,可防SQL注入)
cpp
QSqlQuery query;
// 使用命名占位符
query.prepare("INSERT INTO employees (name, salary, hire_date, avatar) ""VALUES (:name, :salary, :hire_date, :avatar)");// 将C++/Qt类型的值绑定到占位符
query.bindValue(":name", "John Doe");
query.bindValue(":salary", 75000.50);
query.bindValue(":hire_date", QDate::currentDate()); // 绑定QDate// 插入BLOB数据(例如图片)
QFile file("avatar.png");
if (file.open(QIODevice::ReadOnly)) {QByteArray imageData = file.readAll();query.bindValue(":avatar", imageData); // 绑定QByteArray
} else {query.bindValue(":avatar", QVariant(QVariant::ByteArray)); // 绑定空值
}// 执行查询
if (