多线程里使用数据库——QtWidgets

前言

之前没怎么在项目中使用数据库,对数据库这块只了解一点皮毛,只能说能用。这次涉及了在多线程中使用数据库,看了看源码,和吸取了网上的一些经验,整理封装了一下。

环境

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 = qLoadPlugin<QSqlDriver, 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 QHash<Qt::HANDLE, SQLiteHelper*> databaseMap;//所有数据库链接,key: 线程ID, //value 数据库操作实例指针}

.cpp文件

QMutex SQLiteHelper::mutexSql;
QHash<Qt::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()";QList<Qt::HANDLE> keys = databaseMap.keys();for(int i= 0; i<keys.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);QMap<QString,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();

 结束语

根据自己的理解封装的代码,后续根据实践再调整。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/31600.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

怎么裁剪视频大小尺寸?简单的裁剪方法分享

怎么裁剪视频的画面大小尺寸呢&#xff1f;有时当我们下载下来一段视频&#xff0c;由于视频的画面大小比例不同&#xff0c;会有很多的黑边&#xff0c;我们不管是观看还是进行二次编辑都非常影响体验&#xff0c;而调整视频画面比例以适应观众的设备或平台&#xff0c;比如将…

绽放趋势:Python折线图数据可视化艺术

文章目录 一 json数据格式1.1 json数据格式认识1.2 Python数据和Json数据的相互转换 二 pyecharts模块2.1 pyecharts概述2.2 pyecharts模块安装 三 pyecharts快速入门3.1 基础折线图3.2 pyecharts配置选项3.2.1 全局配置选项 3.4 折线图相关配置3.4.1 .add_yaxis相关配置选项3.…

检测文本是否由AI生成,GPT、文心一言等均能被检测

背景 目前很多机构推出了ChatGPT等AI文本检测工具&#xff0c;但是准确率主打一个模棱两可&#xff0c;基本和抛硬币没啥区别。 先说结论&#xff0c;我们对比了常见的几款AI检测工具&#xff0c;copyleaks检测相比较而言最准确。 检测文本 AI文本片段1 来源&#xff1a;G…

【Opencv入门到项目实战】(九):项目实战|信用卡识别|模板匹配|(附代码解读)

所有订阅专栏的同学可以私信博主获取源码文件 文章目录 0.背景介绍1.模板处理1.1模板读取1.2预处理1.3轮廓计算 2.输入图像处理2.1图形读取2.2预处理2.3轮廓计算2.4计算匹配得分 3.小结 0.背景介绍 接下来我们正式进入项目实战部分&#xff0c;这一章要介绍的是一个信用卡号识…

五粮液快闪酒吧亮相大运会,在传承中彰显创新精神

摇风、糖塑、弄窑、趣闻、琉璃、沉香、绣彩、刻花......一座以“中国风&#xff0c;巴蜀韵”为主题的快闪酒吧&#xff0c;是五粮液献给中外来宾的“和美之礼”&#xff0c;一项项身临其境的传统文化体验让全球友人仿佛“梦回大唐盛世&#xff0c;再现繁华风尚”。 &#xff0…

面试热题(反转链表)

给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 链表的题&#xff0c;大部分都可以用指针或者递归可以做&#xff0c;指针如果做不出来的话&#xff0c;…

【腾讯云 Cloud Studio 实战训练营】深度体验 | 使用腾讯云 Cloud Studio 快速构建 Vue + Vite 完成律师 H5 页面

【腾讯云 Cloud Studio 实战训练营】深度体验 | 使用腾讯云 Cloud Studio 快速构建 Vue Vite 完成律师 H5 页面 写在前面的话一、腾讯云 Cloud Studio 介绍1.1 Cloud Studio 应用场景1.2 Cloud Studio 开发优势 二、沉浸式体验开发快速构建 H5 页面2.1 注册与登录 Cloud Studi…

React Dva项目小优化之redux-action

之前 我们讲过 models 接下啦 我们来给大家讲一个新的库 这个库的话 有最好 没有影响也不大 它主要是帮助我们处理 action的 我们直接在 GitHub 官网上搜索 redux-action 我们搜出来 第一个就是 从星数来看 还是非常优秀的 我们拉下来 找到这个Documentation 然后点击进去 进…

自然语言处理从入门到应用——LangChain:记忆(Memory)-[记忆的类型Ⅰ]

分类目录&#xff1a;《自然语言处理从入门到应用》总目录 会话缓存记忆ConversationBufferMemory 本节将介绍如何使用对话缓存记忆ConversationBufferMemory。这种记忆方式允许存储消息&#xff0c;并将消息提取到一个变量中&#xff0c;我们首先将其提取为字符串&#xff1a…

JVM垃圾回收篇-垃圾回收器

JVM垃圾回收篇-垃圾回收器 串行垃圾回收器 Serial串行&#xff1a;为单线程环境设计且只使用一个线程进行垃圾回收&#xff0c;会暂停所有用户的线程&#xff0c;所以不适合服务器环境&#xff0c;适用于堆内存小&#xff0c;适合于个人电脑 开启串行垃圾回收 -XX:UseSeria…

Spring整合MyBatis(详细步骤)

Spring与Mybatis的整合&#xff0c;大体需要做两件事&#xff0c; 第一件事是:Spring要管理MyBatis中的SqlSessionFactory 第二件事是:Spring要管理Mapper接口的扫描 具体的步骤为: 步骤1:项目中导入整合需要的jar包 <dependency><!--Spring操作数据库需要该jar包…

2023年初中信息技术学科暑假备课

目录 2023年初中信息技术学科暑假备课1. 创意空间1.1 教师的空间1.2 学生的空间1.3 关于FTP服务器设置 2. 什么是编程2.1 编程语言2.2 人人都应学好编程2.3. 编程难吗&#xff1f;2.4 python用途 3. 开发环境3.1 打开IDLE3.2 IDLE窗口3.2.1 shell窗口和编辑窗口 4. 项目式教学4…

android报java.lang.UnsatisfiedLinkError错误大全

1、java.lang.UnsatisfiedLinkError: method:logWrite, sig:(Lcom/tencent/mars/xlog/Xlog$XLoggerInfo;Ljava/lang/String;)V 完整错误日志如下&#xff1a; java.lang.UnsatisfiedLinkError: method:logWrite, sig:(Lcom/tencent/mars/xlog/Xlog$XLoggerInfo;Ljava/lang/St…

使用雅可比行列式方法求Henon映射的lyapunov exponent

雅可比行列式方法 计算Henon映射的Lyapunov exponent图谱,算法描述为: 0:初始化:初始化用到的值。参数a:[0,1.4],b:0.3,初始值x和y:1,迭代次数M:2000。 1:遍历参数a:计算不同a值所对应的Henon映射的Lyapunov exponent图谱。 2:迭代M次: 计算得到Henon映射的…

【VSCode】查看二进制文件

1.安装插件Hex Editor 2.打开二进制文件 3.执行Hex Editor命令

篇十九:迭代器模式:遍历集合

篇十九&#xff1a;"迭代器模式&#xff1a;遍历集合" 开始本篇文章之前先推荐一个好用的学习工具&#xff0c;AIRIght&#xff0c;借助于AI助手工具&#xff0c;学习事半功倍。欢迎访问&#xff1a;http://airight.fun/。 另外有2本不错的关于设计模式的资料&…

qmake cmake mingw32-make make介绍

简介makefile makefile是自动化编译时&#xff0c;实现编译需要的规则文件&#xff0c;可通过make&#xff0c;nmake&#xff0c;mingw32-make依据它来批处理编译。 自动化编译工具 make是linux环境下的命令&#xff0c;也被称为GNU Make&#xff0c;Windows环境下无此命令。在…

通用FIR滤波器的verilog实现(内有Lowpass、Hilbert参数生成示例)

众所周知&#xff0c;Matlab 中的 Filter Designer 可以直接生成 FIR 滤波器的 verilog 代码&#xff0c;可以方便地生成指定阶数、指定滤波器参数的高通、低通、带通滤波器&#xff0c;生成的 verilog 代码也可以指定输入输出信号的类型和位宽。然而其生成的代码实在算不上美观…

腾讯云从业者认证考试考点——云存储产品

文章目录 存储产品功能云存储产品概述存储产品存储网关存储服务 存储分类按存储方式分按存储频率分 云存储与传统存储的区别功能需求性能需求容量扩展数据共享 云硬盘CBS产品概述归档存储和文件存储归档存储CAS文件存储CFS 对象存储存储网关存储网关的分类 云数据迁移CDM日志服…

统计列表加小计

提供个思路&#xff0c;欢迎其他大佬指正 注意使用 排序&#xff08;seq&#xff09;&#xff0c;group by&#xff0c;union all SELECTf.* FROM(SELECTcus_id,max( cusname ) cusname,NULL dodate,sum( money ) sumMoney,NULL payed,NULL unpayed,1 seq FROMtb_outbase GRO…