网站的地图要怎么做,用什么软件做网站设计,共振设计公司官网,北京发布重磅消息前言
之前没怎么在项目中使用数据库#xff0c;对数据库这块只了解一点皮毛#xff0c;只能说能用。这次涉及了在多线程中使用数据库#xff0c;看了看源码#xff0c;和吸取了网上的一些经验#xff0c;整理封装了一下。 环境
Qt5.15.2 QSqlDatabase原理
因为不太懂数…前言
之前没怎么在项目中使用数据库对数据库这块只了解一点皮毛只能说能用。这次涉及了在多线程中使用数据库看了看源码和吸取了网上的一些经验整理封装了一下。 环境
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.lockForRead();QSqlDatabase db dict-value(name);dict-lock.unlock();if (!db.isValid())return db;if (db.driver()-thread() ! QThread::currentThread()) {qWarning(QSqlDatabasePrivate::database: requested database does not belong to the calling thread.);return QSqlDatabase();}if (open !db.isOpen()) {if (!db.open())qWarning() QSqlDatabasePrivate::database: unable to open database: db.lastError().text();}return db;
}
移除连接
移除连接只要连接池中有此连接名就会移除连接。如果有其他地方还在使用只会警告此连接该失效还是会失效。
void QSqlDatabase::removeDatabase(const QString connectionName)
{QSqlDatabasePrivate::removeDatabase(connectionName);
}void QSqlDatabasePrivate::removeDatabase(const QString name)
{QConnectionDict *dict dbDict();Q_ASSERT(dict);QWriteLocker locker(dict-lock);if (!dict-contains(name))return;invalidateDb(dict-take(name), name);
}
void QSqlDatabasePrivate::invalidateDb(const QSqlDatabase db, const QString name, bool doWarn)
{if (db.d-ref.loadRelaxed() ! 1 doWarn) {qWarning(QSqlDatabasePrivate::removeDatabase: connection %s is still in use, all queries will cease to work., name.toLocal8Bit().constData());db.d-disable();db.d-connName.clear();}
} 有时候数据库的对象和移除数据库连接在同一个函数内就会有此警告如果不想打印此警告可将数据库的对象用大括号括起来
将
void test()
{QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE,QString(Two));db.open();db.close();QSqlDatabase::removeDatabase(QString(Two));
}
改为
void test()
{{QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE,QString(Two));db.open();db.close();}QSqlDatabase::removeDatabase(QString(Two));
} 注意事项
在多线程中操作数据库前需要注意以下几点
QSqlDatabase对象和QSqlQuery 对象只能在创建所在线程内使用。连接使用完后记得删除QSqlDatabase::removeDatabase。
关于资源竞争问题的异常
在网上查资料时看到“线程内注册与连接数据库的存在竞争问题Qt 会动态的加载数据库的plugin, 加载 plug in 的部分涉及到对本地库文件的管理这一部分出现了竞争从 addDatabase / database到 open 的部分要保证其原子性”。
我看了源码只有在初始化时而且仅限addDatabse第一个参数为数据库驱动类型时使用了插件加载数据库驱动。
本想复现所说的资源竞争的问题写了一个Demo两个线程内同时执行了数百次addDatabase、open、removeDatabase操作未加锁也没出现异常。
但是害怕在此出问题所以在后续封装时还是加了锁。
QSqlDatabase QSqlDatabase::addDatabase(const QString type, const QString connectionName)
{QSqlDatabase db(type);QSqlDatabasePrivate::addDatabase(db, connectionName);return db;
}QSqlDatabase::QSqlDatabase(const QString type)
{d new QSqlDatabasePrivate(this);d-init(type);
}void QSqlDatabasePrivate::init(const QString type)
{drvName type;if (!driver) {DriverDict dict QSqlDatabasePrivate::driverDict();for (DriverDict::const_iterator it dict.constBegin();it ! dict.constEnd() !driver; it) {if (type it.key()) {driver ((QSqlDriverCreatorBase*)(*it))-createObject();}}}if (!driver loader())driver qLoadPluginQSqlDriver, QSqlDriverPlugin(loader(), type);if (!driver) {qWarning(QSqlDatabase: %s driver not loaded, type.toLatin1().data());qWarning(QSqlDatabase: available drivers: %s,QSqlDatabase::drivers().join(QLatin1Char( )).toLatin1().data());if (QCoreApplication::instance() nullptr)qWarning(QSqlDatabase: an instance of QCoreApplication is required for loading driver plugins);driver shared_null()-driver;}
}
封装
根据QSqlDatabase的一些特性封装了一个单例模式的数据库管理类
由于创建的连接只能在本线程内使用所以每个线程都会创建一个连接根据连接名跟数据库连接一一对应的关系并且线程ID的唯一性将线程ID作为连接名。考虑到后续释放内存移除数据库连接用容器保存数据。
.h文件
class SQLiteHelper
{public:static SQLiteHelper* GetInstance();/*** brief removeDatabases 释放内存*/static void removeDatabases();private:SQLiteHelper();~ SQLiteHelper();public:/*** brief insertTableData 表格内插入数据* param tableName 表名* param rowData 需要插入的一行数据* param id 返回的自增的ID值* return*/bool insertTableData(const QString tableName,const QVariantMap rowData ,int id);...private:static QMutex mutexSql;QString m_strConnName;static QHashQt::HANDLE, SQLiteHelper* databaseMap;//所有数据库链接,key: 线程ID //value 数据库操作实例指针}
.cpp文件
QMutex SQLiteHelper::mutexSql;
QHashQt::HANDLE, SQLiteHelper* SQLiteHelper::databaseMap;SQLiteHelper *SQLiteHelper::GetInstance()
{if(!databaseMap.contains(QThread::currentThreadId())) {databaseMap.insert(QThread::currentThreadId(), new SQLiteHelper());}return databaseMap[QThread::currentThreadId()];
}void SQLiteHelper::removeDatabases()
{qDebug()SQLiteHelper::removeDatabases();QListQt::HANDLE keys databaseMap.keys();for(int i 0; ikeys.count();i){Qt::HANDLE id keys[i];//释放内存delete databaseMap.take(id);QSqlDatabase::removeDatabase(QString::number(long(id)));}
}SQLiteHelper::SQLiteHelper()
{mutexSql.lock();m_strConnName QString::number((long)QThread::currentThreadId());QSqlDatabase database QSqlDatabase::addDatabase(QSQLITE, m_strConnName);database.setDatabaseName(car.db);mutexSql.unlock();
}SQLiteHelper::~SQLiteHelper()
{
}bool SQLiteHelper::insertTableData(const QString tableName, const QVariantMap rowData,int id)
{bool r false;QString fieldNames,placeholderVals;QStringList strList rowData.keys();for(int i 0; i strList.count(); i){const QString name strList[i];fieldNames.append(name);placeholderVals.append(QStringLiteral(:%1).arg(name));if(i ! (strList.count()-1)){fieldNames.append(,);placeholderVals.append(,);}}QString sqlStr QString(INSERT INTO %1 (%2) VALUES (%3);).arg(tableName,fieldNames,placeholderVals);QSqlDatabase sqlDb QSqlDatabase::database(m_strConnName);if(!sqlDb.isOpen()){mutexSql.lock();sqlDb.open();mutexSql.unlock();}QSqlQuery sqlQuery(sqlDb);sqlQuery.prepare(sqlStr);QMapQString,QVariant::const_iterator it rowData.constBegin();for(; it ! rowData.constEnd(); it){sqlQuery.bindValue(QString(:%1).arg(it.key()),it.value());}r sqlQuery.exec();if(r){sqlStr QString(select last_insert_rowid() from %1).arg(tableName);if(sqlQuery.exec(sqlStr)){if(sqlQuery.next()){id sqlQuery.value(0).toInt();}}}sqlDb.close();return r;
}...
使用
数据库添加一行数据示例 QVariantMap varMap;varMap.insert(xm,QStringLiteral(张晓明));varMap.insert(xb,QStringLiteral(男));varMap.insert(sfzh,QStringLiteral(123123));SQLiteHelper* sqlHelper SQLiteHelper::GetInstance();for(int i 0; i size; i){int id -1;sqlHelper-insertTableData(studentInfo,varMap,id);qDebug()id;}
在不再使用数据库时释放内存移除连接
SQLiteHelper::removeDatabases(); 结束语
根据自己的理解封装的代码后续根据实践再调整。