4、数据库连接管理:添加/移除数据库连接、连接命名、默认连接、多连接管理
数据库连接管理,说白了就是管好你程序跟数据库之间的那条「管道」。我刚开始做Qt项目时,觉得连接数据库不就是一句QSqlDatabase::addDatabase()的事吗?后来项目一复杂,多个数据库、多个连接同时跑,才发现这里面的坑真不少。今天咱们就把这块彻底捋清楚。
4.1 连接的本质:一个名字对应一个管道
Qt里每个数据库连接都有一个名字。默认情况下,你如果不指定名字,Qt会给你一个叫defaultConnection的默认连接。我个人习惯是,哪怕只有一个连接,也给它起个明确的名字,比如"main_db"。为什么?因为半年后你回头看代码,看到defaultConnection你会一脸懵。
核心概念:QSqlDatabase 对象其实是个「连接句柄」,它不直接管理底层数据库驱动。真正的连接资源由 Qt 内部维护,你通过连接名来操作它。
4.2 添加数据库连接
添加连接很简单,但有几个细节要注意。看代码:
// 添加一个 SQLite 连接,名字叫 "my_sqlite"
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "my_sqlite");
db.setDatabaseName("mydata.db");
if (!db.open()) {
qWarning() << "连接失败:" << db.lastError().text();
} else {
qDebug() << "连接成功!";
}
这里有个坑——addDatabase()的第二个参数就是连接名。如果你不传,Qt会把它当作默认连接。但如果你重复添加同一个名字,Qt会直接崩溃。嗯,我当年就在这上面栽过跟头。
注意:不要重复添加同名连接!添加前最好检查一下连接是否已存在。
4.3 检查连接是否存在
我建议每次添加连接前,先做个检查:
QString connName = "my_sqlite";
if (QSqlDatabase::contains(connName)) {
qDebug() << "连接已存在,直接使用";
QSqlDatabase db = QSqlDatabase::database(connName);
} else {
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", connName);
db.setDatabaseName("mydata.db");
db.open();
}
这样做的好处是,你可以在程序的不同模块里安全地获取同一个连接,而不用担心重复创建。
4.4 默认连接:省事但别滥用
默认连接就是那个没有名字的连接。你调用QSqlDatabase::addDatabase("QSQLITE")(不传第二个参数),它就变成了默认连接。之后你用QSqlDatabase::database()(不传参数)就能拿到它。
说实话,小项目用默认连接挺方便的。但一旦项目上了规模,我强烈建议你给每个连接起名。为什么?因为默认连接只能有一个,你后添加的会覆盖前面的。我曾经在一个模块里用了默认连接,另一个模块也用了,结果两个模块互相覆盖,查了半天才发现。
我的建议:哪怕只有一个连接,也给它起个名字。比如 "primary" 或 "main"。这样代码更清晰,也方便以后扩展。
4.5 移除数据库连接
移除连接比添加要小心。你不能在连接还在使用的时候直接删。正确的做法是:
// 先关闭连接
{
QSqlDatabase db = QSqlDatabase::database("my_sqlite");
if (db.isOpen()) {
db.close();
}
}
// 再移除连接
QSqlDatabase::removeDatabase("my_sqlite");
注意那个大括号——QSqlDatabase对象离开作用域后才会释放。如果你不这样做,Qt会打印一条警告:"QSqlDatabasePrivate::removeDatabase: connection 'my_sqlite' is still in use, all queries will cease to work."。我见过有人忽略这个警告,结果程序跑着跑着就崩了。
重要:移除连接前,确保所有基于该连接的 QSqlQuery 对象也已经被销毁。否则会出现悬空指针问题。
4.6 多连接管理:实战中的场景
多连接管理在实际项目中太常见了。比如我做过一个系统,需要同时连接一个 MySQL 主库和一个 SQLite 本地缓存库。这时候多连接就派上用场了。
下面是一个典型的多连接管理类:
class DatabaseManager {
public:
bool addConnection(const QString &name, const QString &type,
const QString &host, int port,
const QString &dbName,
const QString &user, const QString &pass) {
if (QSqlDatabase::contains(name)) {
qWarning() << "连接" << name << "已存在";
return false;
}
QSqlDatabase db = QSqlDatabase::addDatabase(type, name);
db.setHostName(host);
db.setPort(port);
db.setDatabaseName(dbName);
db.setUserName(user);
db.setPassword(pass);
if (!db.open()) {
qWarning() << "打开连接失败:" << db.lastError().text();
QSqlDatabase::removeDatabase(name);
return false;
}
return true;
}
void removeConnection(const QString &name) {
if (!QSqlDatabase::contains(name)) return;
{
QSqlDatabase db = QSqlDatabase::database(name);
if (db.isOpen()) db.close();
}
QSqlDatabase::removeDatabase(name);
}
QSqlDatabase getConnection(const QString &name) {
if (!QSqlDatabase::contains(name)) {
qWarning() << "连接不存在:" << name;
return QSqlDatabase();
}
return QSqlDatabase::database(name);
}
QStringList connectionNames() const {
return QSqlDatabase::connectionNames();
}
private:
// 这个类不需要成员变量,Qt 内部维护了连接池
};
这个类封装了添加、移除、获取连接的操作。你想想看,如果每个模块都自己去调addDatabase,那代码得多乱?
4.7 多连接下的查询操作
有了多连接,查询时就要指定用哪个连接:
QSqlDatabase db = QSqlDatabase::database("my_sqlite");
QSqlQuery query(db);
query.exec("SELECT * FROM users");
如果你不传db参数,QSqlQuery会使用默认连接。在多连接环境下,这很容易出错。我习惯在每次创建QSqlQuery时都显式传入连接对象,哪怕只有一个连接。
4.8 连接池的概念
Qt 本身没有内置连接池,但你可以自己实现一个简单的。核心思路是:维护一组连接,按需分配,用完后归还。不过对于大多数桌面应用,手动管理几个连接已经足够了。如果你做的是高并发的服务端应用,那建议用专门的连接池库。
小技巧:可以用 QMap<QString, QSqlDatabase> 来管理连接,但要注意线程安全。Qt 的数据库连接不是线程安全的,每个线程应该使用自己的连接。
4.9 知识体系总览
下面这张图帮你理清本章的核心逻辑:
4.10 总结一下
数据库连接管理看似简单,但细节决定成败。记住几个关键点:
- 给连接起名,别偷懒用默认连接
- 添加前检查,避免重复创建导致崩溃
- 移除时注意作用域,先关再删
- 多连接用管理类封装,别让连接散落在各处
- 查询时显式指定连接,别依赖默认行为
这些经验都是我一个个坑踩出来的。你按这个思路来,数据库连接这块基本不会出大问题。
可可精品资料库