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 知识体系总览

下面这张图帮你理清本章的核心逻辑:

数据库连接管理知识体系 连接管理核心 添加连接 addDatabase(type, name) 检查 contains() 避免重复 移除连接 先 close() 再 removeDatabase() 注意作用域释放 默认连接 不传连接名即默认 多连接管理 封装管理类统一操作

4.10 总结一下

数据库连接管理看似简单,但细节决定成败。记住几个关键点:

  • 给连接起名,别偷懒用默认连接
  • 添加前检查,避免重复创建导致崩溃
  • 移除时注意作用域,先关再删
  • 多连接用管理类封装,别让连接散落在各处
  • 查询时显式指定连接,别依赖默认行为

这些经验都是我一个个坑踩出来的。你按这个思路来,数据库连接这块基本不会出大问题。

可可精品资料库