前言
之前没怎么在项目中使用数据库,对数据库这块只了解一点皮毛,只能说能用。这次涉及了在多线程中使用数据库,看了看源码,和吸取了网上的一些经验,整理封装了一下。
环境
Qt5.15.2
QSqlDatabase原理
因为不太懂数据库连接的使用,就看了源码(Qt5.15.2版本源码),只是简单的看了一下,了解到:数据库连接是保存在一个容器中,可以称之为数据库连接池,它负责分配、管理和释放连接。这样的话,就可以重复使用数据库连接,而不需每次创建新的连接。
创建连接
创建连接源码如下,QConnectionDict 为所说的连接池,在添加连接之前使用了锁(QWriteLocker locker(&dict->lock);),所以它是线程安全的。若是连接名重复,会先移除之前的连接,再添加新的连接。
QSqlDatabase QSqlDatabase::addDatabase(const QString &type, const QString &connectionName)
{
QSqlDatabase db(type);
QSqlDatabasePrivate::addDatabase(db, connectionName);
return db;
}
void QSqlDatabasePrivate::addDatabase(const QSqlDatabase &db, const QString &name)
{
QConnectionDict *dict = dbDict();
Q_ASSERT(dict);
QWriteLocker locker(&dict->lock);
if (dict->contains(name)) {
invalidateDb(dict->take(name), name);
qWarning("QSqlDatabasePrivate::addDatabase: duplicate connection name '%s', old "
"connection removed.", name.toLocal8Bit().data());
}
dict->insert(name, db);
db.d->connName = name;
}
获取数据库连接
根据连接名获取之前创建的数据库连接,默认打开数据库连接。还可以看到里面有判断之前创建的数据库实例的线程是否为当前线程,如果不是,就返回了空的数据库实例,也就是说创建的数据库实例只能在本线程内使用。
我在网上的博客
https://www.cnblogs.com/findumars/p/5634462.html
看到说
“一个线程创建的数据库对象(如 addDatabase 的返回值)只能在同一线程使用,但是,addDatabase 注册的连接(名字是开发者定)可以跨线程使用,唯一需要注意的是,在调用全局方法的时候,要有原子保护。”
我理解的意思是可以在其他线程中通过连接名获取此连接,也就是使用database(),但是源码也看到了,连接名是跟addDatabase返回的db(数据库对象)成对存在连接池中的,所以就不存在“连接本身用名字可以多线程使用”。
所以,在多线程中使用数据库时候应该保持每个线程都使用一次addDatabase创建连接,创建后,可在当前线程中使用database(connectionName)获取连接,使用数据库。
QSqlDatabase QSqlDatabase::database(const QString& connectionName, bool open)
{
return QSqlDatabasePrivate::database(connectionName, open);
}
QSqlDatabase QSqlDatabasePrivate::database(const QString& name, bool open)
{
const QConnectionDict *dict = dbDict();
Q_ASSERT(dict);
dict->lock.lo