2. Qt 使用 SQLite

数据库是存放数据的仓库。它的存储空间很大,可以存放百万条、千万条、上亿条数据。 但是数据库并不是随意地将数据进行存放,是有一定的规则的,否则查询的效率会很低。

当今世界是一个充满着数据的互联网世界,充斥着大量的数据。即这个互联网世界就是数据世界。 数据的来源有很多,比如出行记录、消费记录、浏览的网页、发送的消息等等。除了文本类型的数据,图像、音乐、声音都是数据。

数据库是一个按数据结构来存储和管理数据的计算机软件系统。

数据库的概念实际包括两层意思:

  • 数据库是一个实体,它是能够合理保管数据的“仓库”,用户在该“仓库”中存放要管理的事务数据,“数据”和“库”两个概念结合成为数据库。

  • 数据库是数据管理的新方法和技术,它能更合适的组织数据、更方便的维护数据、更严密的控制数据和更有效的利用数据。

常用的数据库:Oracle、MySQL(MongoDB)、Microsoft SQL Server、SQlite。

虽说这些数据库软件各不相同,但是这些数据库都是使用SQL语句来进行数据的增删改查的。首先我们来了解一个数据库软件SQLite。

2.1. SQLite

SQLite是一款轻量级数据库,是一个关系型数据库(RDBMS)管理系统,它包含在一个相对小的C库中, 实现了自给自足的、无服务器的、零配置的、事务性的SQL数据库引擎。 在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。

它能够支持Windows/Linux/Unix/Android/IOS等等主流的操作系统,同时能够跟很多程序语言相结合,比如C#、PHP、Java等, 更重要的是 SQLite文件格式稳定,跨平台且向后兼容,开发人员保证至少在2050年之前保持这种格式。 而且速度极快,目前是在世界上最广泛部署的SQL 数据库,据官网统计活跃使用的SQLite数据库超过1万亿,

更重要的是SQLite源代码不受版权限制,任何人都可以免费使用于任何目的。

大家可以参考官网的介绍: https://www.sqlite.org/index.html

2.1.1. SQLite安装

我们要在Debian开发板上安装SQLite,使用的版本是SQLite3。 SQLite的一个重要的特性是零配置的,这意味着不需要复杂的安装或管理,直接一条命令安装即可。

为了确保我们的版本是最新版本,让我们使用apt命令更新本地apt包索引和升级系统:

sudo apt-get update
sudo apt-get -y upgrade

-y 标志将确认我们同意所有要安装的项目。

sudo apt-get -y install sqlite3

因为SQLite非常小,很快就安装完了,可以看到安装完成的最后输出了SQLite的版本信息,所以安装的版本是 3.27.2-3

···
Preparing to unpack .../sqlite3_3.27.2-3_armhf.deb ...
Unpacking sqlite3 (3.27.2-3) ...
Setting up sqlite3 (3.27.2-3) ...

在Windows上也有一款非常好用的可视化软件:sqlitestudio 可以用来验证,测试我们创建的数据库。

2.1.2. SQLite使用

我们简单讲解 SQLite 编程所使用的简单却有用的命令,这些命令被称为 SQLite 的点命令。

在终端运行 sqlite3 命令,可以发现进入了 SQLite命令终端,并且显示了SQLite版本相关的信息

在 SQLite 命令提示符下,我们可以使用各种 SQLite的点命令,比如我们输入一个 .help 命令, 这是一个帮助命令,可以帮助开发者查看 SQLite 支持所有的点命令,注意,这个help命令前有一个 的,这也是为什么这些命令被称之为点命令的原因。

当输入 .help 命令后,可以看到非常多的点命令:

➜  ~ sqlite3
SQLite version 3.27.2 2019-02-25 16:06:06
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILE name" to reopen on a persistent database.
sqlite> .help
.archive ...             Manage SQL archives
.auth ON|OFF             Show authorizer callbacks
.backup ?DB? FILE        Backup DB (default "main") to FILE
.bail on|off             Stop after hitting an error.  Default OFF
.binary on|off           Turn binary output on or off.  Default OFF
.cd DIRECTORY            Change the working directory to DIRECTORY
.changes on|off          Show number of rows changed by SQL
.check GLOB              Fail if output since .testcase does not match
.clone NEWDB             Clone data into NEWDB from the existing database
.databases               List names and files of attached databases
.dbconfig ?op? ?val?     List or change sqlite3_db_config() options
.dbinfo ?DB?             Show status information about the database
.dump ?TABLE? ...        Render all database content as SQL
.echo on|off             Turn command echo on or off
···
···

其实这里的命令非常多,我只截取了一部分,以下是部分点命令的功能介绍,其实没必要全部记住,在需要的时候通过 .help 命令查询一下即可:

命令

功能

.archive …

管理SQL存档

.auth ON | OFF

显示授权者回调

.backup ?DB? FILE

备份 DB 数据库(默认是 “main”)到 FILE 文件

.restore ?DB? FILE

从FILE文件恢复DB的内容(默认为“ main”)

.databases

列出数据库的名称及其所依附的文件

.dump ?TABLE?

以 SQL 文本格式转储数据库。如果指定了 TABLE 表,则只转储匹配 LIKE 模式的 TABLE 表。

.echo ON|OFF

开启或关闭 echo 命令。

.import FILE TABLE

导入来自 FILE 文件的数据到 TABLE 表中。

.indices ?TABLE?

显示所有索引的名称。如果指定了 TABLE 表,则只显示匹配 LIKE 模式的 TABLE 表的索引。

.log FILE|off

开启或关闭日志。FILE 文件可以是 stderr(标准错误)/stdout(标准输出)。

.mode MODE ?TABLE?

设置输出模式,可选泽的模式:csv 、column 、html、insert、line 、list、list 、tabs 、tcl

.open ?OPTIONS? ?FILE?

关闭现有的数据库并重新打开文件

.output FILE name

发送输出到 FILE name 文件。

.output stdout

发送输出到屏幕。

.print STRING…

逐字地输出 STRING 字符串。

.quit

退出 SQLite 提示符终端。

.read FILE name

从FILE读取输入

.schema ?PATTERN?

显示与PATTERN匹配的CREATE语句

.show

显示各种设置的当前值

.stats ON|OFF

开启或关闭统计。

.tables ?TABLE?

列出与LIKE模式TABLE匹配的表的名称

首先我们创建一个数据库,SQLite 的 sqlite3 命令被用来创建新的 SQLite数据库, 我们不需要任何特殊的权限即可创建一个数据,如果路径下存在相同名字的数据库则直接打开该数据库而不是重新创建。

  • 创建test.db数据库

➜  ~ sqlite3 test.db

SQLite version 3.27.2 2019-02-25 16:06:06
Enter ".help" for usage hints.
sqlite>

2.1.3. 从SQL脚本导入数据库

上面创建的数据库是一个空的数据库, 有些情况下,我们是从脚本导入数据库。

假设现在有这样一个数据库脚本,

我们使用命令行将脚本内容导入到一个新的数据库

sqlite3 new.db < test.sql

注意,这里的new.db 是通过test.sql生成的,它里面的内容与原来的 test.db 完全一致的。

# 从SQL脚本导入数据库
➜  ~ sqlite3 new.db < test.sql

# 生成新的数据库new.db
➜  ~ ls
bin  mountnfs  new.db  qt-app  test.db  test.sql

# 进入新的数据库中并查询数据表
➜  ~ sqlite3 new.db
SQLite version 3.27.2 2019-02-25 16:06:06
Enter ".help" for usage hints.
sqlite> .header on
sqlite> .mode column
sqlite> SELECT * FROM class;
id          name        age         score
----------  ----------  ----------  ----------
1           liuyi       22          610
2           chener      19          621
3           zhangsan    23          601
4           lisi        21          666
5           wangwu      20          629
6           zhaoliu     22          621
7           sunqi       20          611
8           zhouba      22          591
9           wujiu       23          625
10          zhengshi    21          621

2.1.4. 导出数据库为SQL脚本

这一步的操作与上一步操作是相反的,我们可以通过 .dump 命令导出数据库为一个sql脚本,具体操作如下: 在shell终端中(注意不是在sqlite提示符终端)通过以下命令即可导出数据库为sql脚本(前提是存在一个sqlite中存在一个new.db数据库):

sqlite3 new.db .dump > new.sql
# 当前路径下存在test.db数据库
➜  ~ ls
bin  mountnfs  qt-app  new.db

# 导出数据库为sql脚本
➜  ~ sqlite3 new.db .dump > new.sql

# 生成test.sql脚本
➜  ~ ls
bin  mountnfs  qt-app  new.db  new.sql

# 查看脚本的内容,和上面的test.sql完全一致。
➜  ~ cat new.sql
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE class(
   id INT PRIMARY KEY     NOT NULL,
   name           TEXT    NOT NULL,
   age            INT     NOT NULL,
   score          INT
);
INSERT INTO class VALUES(1,'liuyi',22,610);
INSERT INTO class VALUES(2,'chener',19,621);
INSERT INTO class VALUES(3,'zhangsan',23,601);
INSERT INTO class VALUES(4,'lisi',21,666);
INSERT INTO class VALUES(5,'wangwu',20,629);
INSERT INTO class VALUES(6,'zhaoliu',22,621);
INSERT INTO class VALUES(7,'sunqi',20,611);
INSERT INTO class VALUES(8,'zhouba',22,591);
INSERT INTO class VALUES(9,'wujiu',23,625);
INSERT INTO class VALUES(10,'zhengshi',21,621);
COMMIT;

2.2. SQL语句

SQL语句专门是针对数据库操作的脚本语言,上面的sql脚本,就是SQL语句的集合。 数据库的基本操作也无外乎是增删改查。

2.2.1. 创建表

SQLite 的 ``CREATE TABLE``语句用于在任何给定的数据库创建一个新表。 创建基本表,涉及到命名表、定义列及每一列的数据类型。

CREATE TABLE 语句的基本语法如下:

CREATE TABLE database_name.table_name(
   column1 datatype  PRIMARY KEY(one or more columns),
   column2 datatype,
   column3 datatype,
   .....
   columnN datatype,
);
  • database_name.table_name 在数据库中是唯一的。

  • column1, column2, ... columnN :表示的是数据库中的列。

  • datatype :每列的数据类型,可选INT、TEXT、CHAR、REAL等类型。

  • PRIMARY :表示第1列作为主键。

  • KEY :关键字,一个或者多个。

比如下面的语句就创建一个班级,班级中有学生的id,名字、年龄、总分数等列,id作为主键, NOT NULL 的约束表示在表中创建纪录时这些字段不能为NULL,此处使用小写字母表示用户可以修改的内容。

CREATE TABLE class(
   id INT PRIMARY KEY     NOT NULL,
   name           TEXT    NOT NULL,
   age            INT     NOT NULL,
   score          INT
);

这样子创建的数据库就类似一个Excel表格,差不多是这样子的类型:

id          name        age         score
----------  ----------  ----------  ----------

在创建完成后,可以使用 SQLIte 命令中的 ``.tables``命令来验证表是否已成功创建,该命令用于列出附加数据库中的所有表。

sqlite> .tables
class

2.2.2. 插入数据

SQLite 的 INSERT INTO 语句用于向数据库的某个表中添加新的数据行。

INSERT INTO 语句有两种基本语法。

为表中部分列添加对应的值,可以通过以下方法:

INSERT INTO TABLE_NAME (column1, column2, column3,...columnN)
VALUES (value1, value2, value3,...valueN);
  • TABLE_NAME :表示表的名字,是数据库中唯一的。

  • column1, column2, ... columnN :表示要插入数据的表中的列的 名称 ,注意这里是列的名称。

  • value1, value2, value3, ... valueN :表示要插入数据的表中的列的值。

如果要为表中的所有列添加值,我们也可以不需要在 SQLite查询中指定列名称, 但要确保值的顺序与列在表中的顺序一致,SQLite 的 INSERT INTO 语法如下:

INSERT INTO TABLE_NAME VALUES (value1,value2,value3,...valueN);
  • TABLE_NAME:表示表的名字,是数据库中唯一的。

  • value1, value2, value3,…valueN:表示要插入数据的表中的列的值。

注意,插入值的类型要与表中列指定的类型要匹配的。

我们来给class表添加对应的值:

  • 语法1格式:

INSERT INTO class (id, name, age, score)
VALUES (1, 'liuyi', 22, 610);

INSERT INTO class (id, name, age, score)
VALUES (2, 'chener', 19, 621);

INSERT INTO class (id, name, age, score)
VALUES (3, 'zhangsan', 23, 601);

INSERT INTO class (id, name, age, score)
VALUES (4, 'lisi', 21, 666);

INSERT INTO class (id, name, age, score)
VALUES (5, 'wangwu', 20, 629);

INSERT INTO class (id, name, age, score)
VALUES (6, 'zhaoliu', 22, 621);

INSERT INTO class (id, name, age, score)
VALUES (7, 'sunqi', 20, 611);

INSERT INTO class (id, name, age, score)
VALUES (8, 'zhouba', 22, 591);

INSERT INTO class (id, name, age, score)
VALUES (9, 'wujiu', 23, 625);

INSERT INTO class (id, name, age, score)
VALUES (10, 'zhengshi', 21, 621);
  • 语法2格式:

INSERT INTO class VALUES (1, 'liuyi', 22, 610);

INSERT INTO class VALUES (2, 'chener', 19, 621);

INSERT INTO class VALUES (3, 'zhangsan', 23, 601);

INSERT INTO class VALUES (4, 'lisi', 21, 666);

INSERT INTO class VALUES (5, 'wangwu', 20, 629);

INSERT INTO class VALUES (6, 'zhaoliu', 22, 621);

INSERT INTO class VALUES (7, 'sunqi', 20, 611);

INSERT INTO class VALUES (8, 'zhouba', 22, 591);

INSERT INTO class VALUES (9, 'wujiu', 23, 625);

INSERT INTO class VALUES (10, 'zhengshi', 21, 621);

这两个语法得出的结果是一样的,随便选择一个即可。

2.2.3. 查找数据

SQLite 的 SELECT 语句用于从 SQLite数据库表中获取数据,并且以结果表的形式返回数据。

SQLite 的 SELECT 语句的基本语法如下:

SELECT column1, column2, ... columnN FROM table_name;
  • column1, column2, ... columnN :表示要查找数据的表中的列的 名称 ,注意这里是列的名称,当然可以使用 * 符号表示要查找所有的列。

  • table_name :表示要查找数据库中的表名称,它在数据库中是唯一的。

查找刚刚创建的class表操作如下:

sqlite> SELECT * FROM class;

1|liuyi|22|610
2|chener|19|621
3|zhangsan|23|601
4|lisi|21|666
5|wangwu|20|629
6|zhaoliu|22|621
7|sunqi|20|611
8|zhouba|22|591
9|wujiu|23|625
10|zhengshi|21|621

你会发现这些表的格式是很乱,不直观,那么你可以通过 .header 命令显示表头,通过 .mode 设置显示的模式:

.header on
.mode column

然后再次查找所有的内容:

sqlite> SELECT * FROM class;

id          name        age         score
----------  ----------  ----------  ----------
1           liuyi       22          610
2           chener      19          621
3           zhangsan    23          601
4           lisi        21          666
5           wangwu      20          629
6           zhaoliu     22          621
7           sunqi       20          611
8           zhouba      22          591
9           wujiu       23          625
10          zhengshi    21          621

这一次就好看多了,当然你还可以进行排序。

2.2.4. 数据排序

SQLite 的 ORDER BY 语句是用来基于一个或多个列按升序或降序顺序排列数据。

ORDER BY 语句的基本语法如下:

SELECT column-list
FROM table_name
[WHERE condition]
[ORDER BY column1, column2, .. columnN] [ASC | DESC];

比如按照某一列进行排序,此处选择 score 列,ASC表示升序,DESC表示降序:

sqlite> SELECT * FROM class ORDER BY score ASC;

id          name        age         score
----------  ----------  ----------  ----------
8           zhouba      22          591
3           zhangsan    23          601
1           liuyi       22          610
7           sunqi       20          611
2           chener      19          621
6           zhaoliu     22          621
10          zhengshi    21          621
9           wujiu       23          625
5           wangwu      20          629
4           lisi        21          666
sqlite> SELECT * FROM class ORDER BY score DESC;

id          name        age         score
----------  ----------  ----------  ----------
4           lisi        21          666
5           wangwu      20          629
9           wujiu       23          625
2           chener      19          621
6           zhaoliu     22          621
10          zhengshi    21          621
7           sunqi       20          611
1           liuyi       22          610
3           zhangsan    23          601
8           zhouba      22          591

2.3. Qt 使用数据库

现在我们应该对SQLite和SQL有所了解了。下面再以SQLite数据库为例来看看Qt中如何使用数据库。

Qt的SQL类大致可分为3层

分层

作用

驱动层

该层提供了特定数据库和SQL API层之间的底层桥梁

QSqlDriver,QSqlDriverCreator,QSqlDriverCreatorBase,QSqlDriverPlugin和QSqlResult

SQL API层

这些类提供对数据库的访问。使用QSqlDatabase类进行数据库连接,QSqlQuery类实现数据库交互

QSqlDatabase,QSqlQueryQSqlError,QSqlField,QSqlIndex,和QSqlRecord

用户界面层

将数据从数据库链接到数据感知小部件

QSqlQueryModel(只读),QSqlTableModel(读/写),QSqlRelationalTableModel(具有外键支持的读/写)

首先数据库软件非常丰富,数据库软件提供的操作API不尽相同,

例如SQLite为C/C++提供如下接口

# 若该数据库不存在则新建一个数据库
sqlite3_open(const char *filename, sqlite3 **ppDb)
# 执行SQL语句
sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg)
# 关闭之前打开的数据库
sqlite3_close(sqlite3*)
...

而MySQL为C/C++提供这样的接口

# 连接数据库
MYSQL *mysql_real_connect (MYSQL *mysql,const char *host,const char *user, const char *passwd, const char *db, unsigned int port,const char *unix_socket,unsigned long client_flag)
# 执行SQL语句
int mysql_query(MYSQL * mysql,const char * query)
# 关闭数据库连接
void mysql_close(MYSQL * mysql)
...

显然两者用法不相同, 因此Qt不能具体为每一款数据库软件定制接口,而是以数据库插件的形式(驱动层)来适配不同的数据库。 我们来看看SQlite的数据库插件吧。

2.3.1. 数据库插件

在Qt的SQL模块使用驱动程序的插件与几个数据库API进行通信。 Qt默认支持SQLite,MySQL,DB2,Borland InterBase,Oracle,ODBC和PostgreSQL等数据库, 提供了驱动程序源代码,同时可以将其用作编写自己的驱动程序的模型。

我们不妨来看一看SQLite和MySQL的驱动程序代码是否像上面所说通过调用不同API实现连接:

qt-everywhere-src-5.11.3_src/qtbase/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/*
SQLite dbs have no user name, passwords, hosts or ports.
just file names.
*/
bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &conOpts)
{
    Q_D(QSQLiteDriver);
    if (isOpen())
        close();
    ...
    const int res = sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, NULL);

    ...
        if (d->access) {
            sqlite3_close(d->access);
            d->access = 0;
        }

        return false;
    }
}
qt-everywhere-src-5.11.3_src/qtbase/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool QMYSQLDriver::open(const QString& db,const QString& user,const QString& password,const QString& host,int port,const QString& connOpts)
{
    Q_D(QMYSQLDriver);
    if (isOpen())
    close();
    ...
    MYSQL *mysql = mysql_real_connect(d->mysql,
                    host.isNull() ? static_cast<const char *>(0)
                                : host.toLocal8Bit().constData(),
                    user.isNull() ? static_cast<const char *>(0)
                                : user.toLocal8Bit().constData(),
                    password.isNull() ? static_cast<const char *>(0)
                                    : password.toLocal8Bit().constData(),
                    db.isNull() ? static_cast<const char *>(0)
                                : db.toLocal8Bit().constData(),
                    (port > -1) ? port : 0,
                    unixSocket.isNull() ? static_cast<const char *>(0)
                                        : unixSocket.toLocal8Bit().constData(),
                    optionFlags);

    ...
    return true;
}

不难看出,初始化SQLite数据库和MySQL数据库分别调用了sqlite3_open()和mysql_real_connect()。

2.3.1.1. 编译数据库插件

在Qt源码中,configure脚本尝试自动检测计算机上可用的数据库库。 运行configure -help以查看可以构建哪些驱动程序。 您应该获得类似于以下的输出,如果你的计算机上什么数据库也没有安装的话:

Database options:Database options:
-sql-<driver> ........ Enable SQL Enable SQL <driver> plugin. Supported drivers:Supported drivers:
                        db2 ibase mysql oci odbc psql sqlite2 sqlite tds
                        [all auto]
-sqlite .............. Select used sqlite3 Select used sqlite3 [system/qt]

Qt默认是会只会SQLite,SQLite轻便小巧。 在config中添加 -sqlite 配置,最终就会编译出sqlite数据库插件。 按照我们教程搭建的环境,sqlite插件位置在 /opt/qt-everywhere-src-5.11.3/plugins/sqldrivers/ 目录下。

如果你要编译MySQL数据库插件呢,就必须得提供MySQL的头文件mysql.h, MySQL的版本比较多,还需要注意连接那个版本的数据库就用那个版本的头文件。 还有请注意哦,数据库插件适用于MySQL或MariaDB 5及更高版本。

更多的数据库插件请 参考这里

2.3.2. SQL API

有了数据库插件的支持,我们就能访问不同的数据库了。 既然都是数据库,那我们何不统一API呢,是的,Qt也正是这样的做的。 Qt封装了一些类专门用来处理数据库访问。

2.3.2.1. QSqlDatabase

QSqlDatabase类提供了用于通过连接访问数据库的接口。

QSqlDatabase通过调用静态addDatabase()函数来创建数据库连接(即QSqlDatabase的实例), 在其中您可以指定要使用的驱动程序或驱动程序类型(取决于数据库的类型)和连接名称。

创建QSqlDatabase对象后,请使用setDatabaseName(),setUserName(),setPassword(),setHostName(), setPort()和setConnectOptions()来设置连接参数。 然后调用open()激活与数据库的物理连接。

例如QPSQL连接

QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL");
db.setHostName("acidalia");
db.setDatabaseName("customdb");
db.setUserName("mojito");
db.setPassword("J0a1m8");
bool ok = db.open();

如果创建多个数据库连接,则在调用addDatabase()时为每个数据库指定一个唯一的连接名称。 使用带有连接名称的database()来获得该连接,使用带有连接名称的removeDatabase()删除连接。 如果尝试删除其他QSqlDatabase对象引用的连接,则QSqlDatabase将输出警告。 可以使用contains()来查看给定的连接名称是否在连接列表中。

2.3.2.2. QSqlQuery

QSqlQuery封装了从在QSqlDatabase上执行的SQL查询创建,导航和检索数据所涉及的功能。 它可以被用来执行DML(数据操纵语言)语句,例如SELECT,INSERT,UPDATE和DELETE, 以及DDL(数据定义语言)语句,如CREATE TABLE。 它也可以用于执行不是标准SQL的特定于数据库的命令(例如SET DATESTYLE=ISOPostgreSQL)。

例如下面这句从 artist 表查询 country 字段

QSqlQuery query("SELECT country FROM artist");

while (query(query.next()) {
    QString country = query.value((0).toString();
    doSomething(country);
}

QSqlQuery支持准备好的查询执行以及参数值与占位符的绑定,下面是支持的几种绑定方式

使用命名占位符的命名绑定:

QSqlQuery query;
query.prepare("INSERT INTO person (id, forename, surname) VALUES (:id, :forename, :surname)");
query.bindValue(":id", 1001);
query.bindValue(":forename", "Bart");
query.bindValue(":surname", "Simpson");
query.exec();

使用命名占位符的位置绑定:

QSqlQuery query;
query.prepare("INSERT INTO person (id, forename, surname) VALUES (:id, :forename, :surname)");
query.bindValue(0, 1001);
query.bindValue(1, "Bart");
query.bindValue(2, "Simpson");
query.exec();

使用位置占位符(版本1)绑定值:

QSqlQuery query;
query.prepare("INSERT INTO person (id, forename, surname) VALUES (?, ?, ?)");
query.bindValue(0, 1001);
query.bindValue(1, "Bart");
query.bindValue(2, "Simpson");
query.exec();

//或者
query.addBindValue(1001);
query.addBindValue("Bart");
query.addBindValue("Simpson");
query.exec();

将值绑定到存储过程:

QSqlQuery query;
query.prepare("CALL AsciiToInt(?, ?)");
query.bindValue(0, "A");
query.bindValue(1, 0, QSql::Out);
query.exec();
int i = query.boundValue(1).toInt(); // i is 65

更多SQL语句执行请 参考这里

2.3.3. SQL Model

除了QSqlQuery之外,Qt还提供了三个用于访问数据库的更高级别的类, 分别是QSqlQueryModel,QSqlTableModel和QSqlRelationalTableModel。

Model通常和View配合使用,Model/View参考

2.3.3.1. QSqlQueryModel

提供基于SQL查询的只读模型。

QSqlQueryModel model;
model.setQuery("SELECT * FROM employee");

for (int i = 0; i < model.rowCount(); ++i) {
    int id = model.record(i).value("id").toInt();
    QString name = model.record(i).value("name").toString();
    qDebug() << id << name;
}

2.3.3.2. QSqlTableModel

QSqlTableModel提供了一次可在单个SQL表上工作的读写模型。

QSqlTableModel model;
model.setTable("employee");
model.setFilter("salary > 50000");
model.setSort(2, Qt::DescendingOrder);
model.select();

for (int i = 0; i < model.rowCount(); ++i) {
    QString name = model.record(i).value("name").toString();
    int salary = model.record(i).value("salary").toInt();
    qDebug() << name << salary;
}

2.3.3.3. QSqlRelationalTableModel

QSqlRelationalTableModel的行为类似于QSqlTableModel,但允许将列设置为其他数据库表中的外键。

model->setTable("employee");

model->setRelation(2, QSqlRelation("city", "id", "name"));
model->setRelation(3, QSqlRelation("country", "id", "name"));

2.4. 例程说明

野火提供的Qt Demo已经开源,仓库地址在:

文档所涉及的示例代码也提供下载,仓库地址在:

本章例程在 embed_qt_develop_tutorial_code/SQL

例程使用Qt官方的SQL例程。

2.4.1. 编程思路

2.4.2. 代码讲解

embed_qt_develop_tutorial_code/Sql/Book/initdb.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
QSqlError initDb()
{
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName(":memory:");

    if (!db.open())
        return db.lastError();

    QStringList tables = db.tables();
    if (tables.contains("books", Qt::CaseInsensitive)
        && tables.contains("authors", Qt::CaseInsensitive))
        return QSqlError();

    QSqlQuery q;
    if (!q.exec(QLatin1String("create table books(id integer primary key, title varchar, author integer, genre integer, year integer, rating integer)")))
        return q.lastError();
    if (!q.exec(QLatin1String("create table authors(id integer primary key, name varchar, birthdate date)")))
        return q.lastError();
    if (!q.exec(QLatin1String("create table genres(id integer primary key, name varchar)")))
        return q.lastError();

    if (!q.prepare(QLatin1String("insert into authors(name, birthdate) values(?, ?)")))
        return q.lastError();
    QVariant asimovId = addAuthor(q, QLatin1String("Isaac Asimov"), QDate(1920, 2, 1));
    QVariant greeneId = addAuthor(q, QLatin1String("Graham Greene"), QDate(1904, 10, 2));
    QVariant pratchettId = addAuthor(q, QLatin1String("Terry Pratchett"), QDate(1948, 4, 28));

    if (!q.prepare(QLatin1String("insert into genres(name) values(?)")))
        return q.lastError();
    QVariant sfiction = addGenre(q, QLatin1String("Science Fiction"));
    QVariant fiction = addGenre(q, QLatin1String("Fiction"));
    QVariant fantasy = addGenre(q, QLatin1String("Fantasy"));

    if (!q.prepare(QLatin1String("insert into books(title, year, author, genre, rating) values(?, ?, ?, ?, ?)")))
        return q.lastError();
    addBook(q, QLatin1String("Foundation"), 1951, asimovId, sfiction, 3);
    ...
    return QSqlError();
}

初始化数据库,创建了三个表books,authors,genres;并分别向表中插入数据数据,下面是向books表插入数据的函数。

embed_qt_develop_tutorial_code/Sql/Book/initdb.h
1
2
3
4
5
6
7
8
9
void addBook(QSqlQuery &q, const QString &title, int year, const QVariant &authorId,const QVariant &genreId, int rating)
{
    q.addBindValue(title);
    q.addBindValue(year);
    q.addBindValue(authorId);
    q.addBindValue(genreId);
    q.addBindValue(rating);
    q.exec();
}

首先我们准备了一条插入语句:

insert into books(title, year, author, genre, rating) values(?, ?, ?, ?, ?)

然后调用addBook()函数,将缺省的 用参数代替,

addBook(q, QLatin1String("Our Man in Havana"), 1958, greeneId, fiction, 4);

也就是通过addBindValue,将title, year, author, genre, rating分别添加到上面的语句中,这时就成了下面的:

insert into books("Our Man in Havana", 1958, greeneId, fiction, 4);

这就是一条可以执行的sql语句了,最后通过q.exec()执行该语句。

数据库初始化完毕之后,我们新建了一个数据库模型,分别将我们要显示的数据绑定到表格和控件。

model = new QSqlRelationalTableModel(ui.bookTable);

当我们更改条件进行查询的时候,有model自动完成并刷新显示。

2.4.3. 编译构建

build001
  • Ubuntu 选择 Desktop Qt 5.11.3 GCC 64bit 套件,编译运行

  • LubanCat 选择 ebf_lubancat,只编译

注意 当两种构建套件进行切换时,请重新构建项目或清除之前项目。

build002

2.4.4. 运行结果

2.4.4.1. PC实验

直接点击编译并运行程序,结果如下:

result001

2.4.4.2. LubanCat实验

编译程序之后,需要将程序拷贝到LubanCat开发板中,可通过NFS或SCP命令

NFS环境搭建 参考这里

SCP命令如下:

# scp传输文件
# scp 文件名 服务器上的某个用户@服务器ip:/文件保存路径
scp filename server_user_name@192.168.0.205:server_file_path
# 从服务器拉取文件
# scp 服务器上的某个用户@服务器ip:/服务器文件存放路径 拉取文件保存路径
scp server_user_name@192.168.0.229:server_file_path local_path

编译好的程序在 embed_qt_develop_tutorial_code/app_bin/sql 目录中

scp root@192.168.0.174:/home/embed_qt_develop_tutorial_code/app_bin/sql/Book /usr/local/qt-app/

在运行程序之前先安装Qt的sql插件:

sudo apt-get -y install libqt5sql5

在LubanCat运行程序

sudo /usr/local/qt-app/run_myapp.sh /usr/local/qt-app/Book
result002