Qt 系列(七)——数据库

前言

数据存储这块儿不管是B/S还是C/S都是少不了的,而qt中直接有现成的链接数据库的类,当然适配的应该不至于跟Orm一样花里胡哨,常规的Sqllite,SqlServer,Mysql啥的是没问题的。

Qt连接数据库

qt支持的数据库个人常用的驱动名称如下:

驱动类别对应数据库
QSQLITESQLLITE3以上
QODBCSQLSERVER
QMYSQLMYSQL

配置工程

首先来看下如何配置工程,QCreator的话在pro文件中找到QT += 这句。

1
QT += core gui sql

vs的话,类似,在工程属性页对应下图配置。

配置

连接

接下来就可以来实现连接了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// .h
#pragma once
#include <iostream>
#include <vector>
#include <QString>
#include <QtSql/QSqlDatabase>
#include <QtSql/QtSql>
#include <QtSql/QSqlQuery>

class CSqlLiteManager
{
public:
CSqlLiteManager();
~CSqlLiteManager();

bool connectToDb();
bool isConnected();

private:
QSqlDatabase m_connect;
bool m_isConnected = false;
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// .cpp
bool CSqlLiteManager::connectToDb()
{
if (QSqlDatabase::contains("qt_sql_default_connection"))
{
m_connect = QSqlDatabase::database("qt_sql_default_connection");
}
else
{
m_connect = QSqlDatabase::addDatabase("QSQLITE");
}
if (!m_connect.isValid())
{
std::cout << "connect sqllite failed" << std::endl;
return false;
}
m_connect.setDatabaseName("test.db");
if (!m_connect.open())
{
std::cout << "open sqllite failed" << std::endl;
return false;
}
}

调用这个类然后运行程序,不出意外的话会有如下提示(当然如果已经配置了环境变量之类的可能会直接成功):

未加载sqllite

找到qt类库目录,然后从plugins拷走sqldrivers到运行程序目录下,对应层级设置好(个人习惯在exe同层新建plugins然后放置对应qt的插件库)。

在main.cpp中添加读取插件库的方法。

1
2
3
4
5
6
7
8
9
10
11
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QString strLibPath = a.applicationDirPath();
strLibPath += "/plugins/";
a.addLibraryPath(strLibPath);
QtDemo w;
w.show();
return a.exec();
}

再运行程序就可以看到对应目录下有个test.db这个数据库。

建表

新建的数据库里肯定都是空的,所以需要来张表操作下,可以通过数据库管理工具(SQLLite Expert啊,Navicat之类的),这里用的sqllite就直接建个表了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void CSqlLiteManager::createTable()
{
QSqlQuery query;
query.exec(QString("select count(1) from sqlite_master where type='table' and name='person'"));
if (query.next())
{
if (query.value(0).toInt() == 0)
{
QString create_sql = "create table person (id integer primary key autoincrement, number varchar(10), name varchar(20), utype int, addtime datetime default (datetime('now', 'localtime')))";
query.prepare(create_sql);
if (!query.exec())
{
std::cout << "create table failed " << query.lastError().text().toStdString() << endl;
}
}
}
else
{
std::cout << "select table failed " << query.lastError().text().toStdString() << std::endl;
}
}

通过QSqlQuery.lastError() 可以获取到执行的错误信息,方便排查问题。

运行程序,然后我们通过个工具来看下是否建表完成。

建表完成

增删改查

所有的数据相关离不开增删改查,crud工程师的必备技能。

这部分基本上直接上代码了,对应的扩展可以自行臆想。

新增
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool CSqlLiteManager::insertData()
{
bool ret = false;
QSqlQuery query;
QString sql = QString("insert into person (%1) values('%2','%3',%4,'%5')")
.arg("`number`,`name`,`utype`,`addtime`")
.arg("100001")
.arg("test01")
.arg(1)
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"));
if (query.exec(sql))
{
ret = true;
}
else
{
std::cout << "insertData error: " << query.lastError().text().toStdString() << std::endl;
}
return ret;
}
修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool CSqlLiteManager::updateData()
{
bool ret = false;
QSqlQuery query;
QString sql = QString("update person set `name`='test0002' where id=%1")
.arg(1);
if (query.exec(sql))
{
ret = true;
}
else
{
std::cout << "updateData error: " << query.lastError().text().toStdString() << std::endl;
}
return ret;
}
删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool CSqlLiteManager::deleteData()
{
bool ret = false;
QSqlQuery query;
QString sql = QString("delete from person where id=%1")
.arg(2);
if (query.exec(sql))
{
ret = true;
}
else
{
std::cout << "deleteData error: " << query.lastError().text().toStdString() << std::endl;
}
return ret;
}
查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
std::vector<DbViewItem> CSqlLiteManager::getData()
{
std::vector<DbViewItem> data;
QSqlQuery query(m_connect);
QString sql = QString("select * from person order by addtime desc");
if (query.exec(sql))
{
while (query.next())
{
DbViewItem item;
item.id = query.value("id").toInt();
item.number = query.value("number").toString();
item.name = query.value("name").toString();
item.utype = query.value("utype").toInt();
item.addtime = query.value("addtime").toDateTime();
data.push_back(item);
}
}
return data;
}

查询数据

多线程使用

如果在多线程中,为了保持单连接,要用到单例(设计模式这块儿后续有时间再缕),当然也可以一直做短连接,连接->执行->断开,不过一般不习惯这种操作,直接一个长连接保持就行。

在执行sql时进行锁操作,防止多个线程调用同一资源造成的冲突。

1
2
3
4
5
6
// .h
#include <mutex>

// …省略其他
private:
std::mutex lock_mutex;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool CSqlLiteManager::insertData()
{
bool ret = false;
lock_mutex.lock(); // 加锁
QSqlQuery query;
QString sql = QString("insert into person (%1) values('%2','%3',%4,'%5')")
.arg("`number`,`name`,`utype`,`addtime`")
.arg("100001")
.arg("test01")
.arg(1)
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"));
if (query.exec(sql))
{
ret = true;
}
else
{
std::cout << "insertData error: " << query.lastError().text().toStdString() << std::endl;
}
lock_mutex.unlock(); // 解锁
return ret;
}

多个数据库

有些业务比较复杂的,可能会用到多个/多类数据库,如本地sqllite,远程mysql之类的,在一个工程里如果使用多个数据库,要留意几个问题,一个是QSqlQuery的声明,另一个是QSqlDatabase::addDatabase()

1
2
m_connect = QSqlDatabase::addDatabase("QSQLITE","localdb"); // 第二个参数是连接串名称,保持唯一
QSqlQuery query(m_connect); // 指定Query是用哪个连接

相当于是谁的执行用谁的连接,不指定就按照默认的连接(也就是第一个连接),如果工程只有一个数据库连接那就不需要再这么麻烦,当然写上最好,毕竟规范嘛。

小结

大体上数据库相关的操作基本上基础的就是这些,像分页啊查询条件啊聚合啊等等都是看看sql教程就行了,程序这块儿没有啥太多其他的操作,不过有些像数据绑定界面列表这种操作,因人而异吧,个人比较习惯自己写个结构体来接收然后自解析做绑定,后续有新的体会了会再更新调整。


Qt 系列(七)——数据库
http://www.aprilblank.top/qt/database.html
作者
AprilBlank
发布于
2022年7月25日
许可协议