该概览假设你有SQL的基础知识,如select,insert,update和delete。尽管QSqlTableModel类在不需要SQL知识的情况下提供了浏览和编辑数据库的接口,但仍强烈推荐理解SQL。
话题包括:
1.Database Classes:数据库类
2.Connecting to Databases:连接到数据库
SQL Database Drivers:SQL数据库驱动器
3.Executing SQL Statements:执行SQL语句
Data Types for Qt-supported Database Systems:Qt支持的数据库系统的数据类型
4.Using the SQL Model Classes:使用SQL model类
5.Presenting Data in a Table View:在Table View中展示数据
6.Creating Data-Aware Forms:创建数据感知的Forms
一、Database Classes:数据库类
SQL类分为三层:
1)Driver层:该层为SQL API层和数据库提供桥梁。
2)SQL API层:该层提供访问数据库的接口。用 QSqlDatabase类连接数据库。用QSqlQuery类和数据库进行交互。除了上述2个类,还有 QSqlError, QSqlField, QSqlIndex, 和 QSqlRecord.。
3)用户接口层:该层的类把数据库里的数据连接到widget,包括 QSqlQueryModel, QSqlTableModel, 和 QSqlRelationalTableModel。
二、Connecting to Databases:连接到数据库
访问数据库之前,首先要创建并打开一个或多个数据库连接。数据库连接通常用 [连接名] 来标识。对同一个数据库可以有多个连接。QSqlDatabase还支 [默认连接] 的概念,它是个未命名的连接。当调用QSqlQuery或QSqlQuery的方法且其中需要[连接名]参数时,如果不传递一个[连接名],则使用[默认连接]。
当应用程序中只有一个连接时,使用默认连接是最方便的。
创建和打开连接是不同的。创建一个连接是创建一个QSqlDatabase对象,但是为打开前连接是不可用的。下面的代码展示了如何创建一个连接并打开。
第一句是创建连接,最后一句是打开连接,中间是做一些准备。参数“QMYSQL”用来指定数据库驱动器的类型。Qt支持的数据库类型在supported database drivers中。
上面的代码展示的是【默认连接】,应为没有给 addDatabase()传递【连接名】。下面是两个命名的连接:
使用 open() 函数打开连接。如果失败了,则返回false,用 QSqlDatabase::lastError() 来获取错误信息。
当连接建立后,可以在任何地方使用静态函数QSqlDatabase::database() 来获取连接,如果不传递连接名,则返回默认连接。
想要移除连接,则先用 QSqlDatabase::close()关闭连接,然后用静态函数QSqlDatabase::removeDatabase()移除。对于默认连接,用connectionName()获得名字,然后移除。
三、Executing SQL Statements:执行SQL语句
QSqlQuery 类提供了执行SQL语句的接口以及获取查询结果集合的接口。
QSqlQueryModel and QSqlTableModel 提供了访问数据库的更高层次的接口。如果你不熟悉SQL,可以直接跳转到下一节。
要执行SQL语句,简单的创建一个QSqlQuery 对象并调用 QSqlQuery::exec()函数即可。
QSqlQuery 的构造函数接收一个可选的 QSqlDatabase 对象来指定使用哪个连接。上面没有传递该参数,表明使用的是默认连接。
如果exec() 返回false,表明有错误,则调用 QSqlQuery::lastError()来查看。
如何查看查询的结果呢?QSqlQuery提供了对结果集合的访问,每次可以访问一条记录。调用exec() 后,QSqlQuery内部的指针在first record的前面,因此必须调用一次 QSqlQuery::next()来获取第一条记录,然后继续调用 QSqlQuery::next()来获取其他记录,直到返回false。
下面的代码是获取所有记录的典型循环:
QSqlQuery::value() 返回当前记录的一个字段值。字段用zero-based的索引指定。 QSqlQuery::value()返回一个 QVariant, 该类型可以存储很多种 C++ 和 Qt 数据类型,例如 int, QString, and QByteArray.
对于Qt推荐的数据类型,查看此表this table.
可以用 QSqlQuery::next(), QSqlQuery::previous(), QSqlQuery::first(), QSqlQuery::last(), 和 QSqlQuery::seek()来寻找各个记录。 QSqlQuery::at()返回当前记录所在的行号。 QSqlQuery::size() 返回查询结果集合中所有的记录个数(行数)。
QSqlDriver::hasFeature()用来判断数据库驱动器是否支持特定的属性。
如果你只是前向浏览,在exec() 前先调用 QSqlQuery::setForwardOnly(true)能显著的加速对多结果查询。
Insert、Update和Delete记录
除了直接插入,qt还支持命名的占位符和索引的占位符,如下所示。
QSqlQuery::executedQuery()返回DBMS实际执行的语句,可能跟 QSqlQuery里写的不同(但作用相同)。
若果想要插入多条记录,只需要调用 QSqlQuery::prepare() 一次,然后调用 bindValue() 或 addBindValue() 紧跟着 exec() 多次。
占位符的好处除了性能,还不用担心转义字符。
Update和Delete也可以用命名或位置的占位符。
事务:Transaction
如果数据库支持事务,QSqlDriver::hasFeature(QSqlDriver::Transactions) 返回true. 用 QSqlDatabase::transaction() 来启动事务,然后执行SQL语句,最后用 QSqlDatabase::commit() or QSqlDatabase::rollback()来提交或回滚。当使用transaction时,必须先启动transaction然后再创建QSqlQuery,如下图所示。
四、Using the SQL Model Classes:使用SQL model类
除了使用接近底层的 QSqlQuery,Qt还提供了更高层次的类来访问数据库,包括 QSqlQueryModel, QSqlTableModel, 和QSqlRelationalTableModel.
QSqlQueryModel可以基于多表查询,更灵活,而后两者只能基于一张表,不太灵活。
这些类都派生自 QAbstractTableModel (它又派生自 QAbstractItemModel),他们使得在View上(如QListView或QTableView)展示数据更方便。在下一节 Presenting Data in a Table View 中会讨论。
使用这些类的另一个好处是修改数据源时代码简单,例如从数据库改成xml数据。
SQL Query Model
QSqlQueryModel提供了一个基于SQL查询的只读model。
当用setQuery()函数设置查询后,可以用record(int)函数获取单条记录。还可以用data()函数获取数据,也可以用继承自 QAbstractItemModel的函数。
setQuery()可以查询多个table,view等内容,很灵活。
setQuery() 还有一个重载函数,使用 QSqlQuery 对象来实现 QSqlQuery 的所有特性,如prepare函数。
如果数据库不支持返回query size(SQLite就不支持),则rowCount()返回是的当前缓存的行数,而不一定是真实的查询行数,此时要看 canFetchMore()是否为真,然后用 fetchMore()获取更多,直到 canFetchMore()为false。
SQL Table Model
QSqlTableModel提供了一个可读可写的模型,但每次只能工作在一张table上(没有QSqlQueryModel灵活)。
QSqlTableModel 用来浏览和修改单个SQL table。
利用 QSqlTableModel::record(int)来获取表格中的一行记录,用 QSqlTableModel::setRecord() 来修改行。下面的例子是把工资提高10%。
修改数据后用submitAll()提交所有的待处理的改变。
还可以用 QSqlTableModel::data() 和 QSqlTableModel::setData()来访问数据,如下图所示。
下图展示了如何向QSqlTableModel中插入一行数据insertRows(),然后设置数据内容setData(),最后提交数据库submitAll()。
用removeRows()来删除行,然后submitAll()提交给数据库。
当完成对记录的改变,需要调用submitAll()来把修改的数据写入到数据库中。
何时调用submitAll()取决于QSqlTableModel的edit strategy,默认的策略是QSqlTableModel::OnRowChange,就是缓存一行的数据,当用户选择其他行时,更新数据库。 QSqlTableModel::OnManualSubmit 是所有的改变都先缓存在model中,当调用submitAll()时才写数据库;QSqlTableModel::OnFieldChange 是不缓存,直接写数据库。
SQL Relational Tabel Model
QSqlRelationalTableModel是对QSqlTableModel的扩展,它支持外键(foreign key),外键是另一个表的主键。
如下图所示,左边表格的(city, country)都是外键,且是数字,右边表格则把外键修改成了用户可读的内容。
下图展示了 QSqlRelationalTableModel 如何设置的实例:
setRelation()函数用来设置外键,会用到 QSqlRelation对象。
五、Presenting Data in a Table View:在Table View中展示数据
QSqlQueryModel,QSqlTableModel和QSqlRenationalTableModel类可以用来作为Qt View的数据源,如QListView, QTableView, 和QTreeView,其实最常用的还是 QTableView,因为SQL结果就是table。
下面是创建一个基于SQL data model的视图,使用setModel()函数:
如果model是read-write的模型(如QSqlTableModel),view允许用户编辑字段。当然也可以通过如下代码禁用:
视图会显示表头,想要改变表头的文字,调用model的setHeaderData() ,默认情况下表头是数据库中table的字段头。
QTableView 还有垂直的表头来时识别行号。如果调用 QSqlTableModel::insertRows(),新的行会显示*,直到调用 submitAll() 或用户自动提交更改( QSqlTableModel::OnRowChange)。
同样,当调用 removeRows()时,行前面会有!。
视图中的item用代理来渲染,默认的代理是QStyledItemDelegate,它可以处理绝大多数的数据类型(如int,QString,QImage等)。当用户编辑view中的item时,代理同时负责提供编辑器widget(如combobox)。你也可以创建自己的代理,子类化 QAbstractItemDelegate or QStyledItemDelegate即可。
QSqlTableModel只能操作一个table。如果你需要一个能够读写且能够处理任意查询结果的model,你可以子类化QSqlQueryModel并实现其中的flags() 和 setData() 函数来使他能够read-write. 下面两个函数能够让model查询结果的字段1和字段2可编辑。
其中setFirstName()函数定义如下:
查看 Query Model 示例可以得到完整的代码。
子类化一个model可以实现很多自定义的功能:例如给每个item提供tooltips,改变背景颜色、提供计算值等等。
如果需要解决外键的显示,则可以用QSqlRelationalTableModel。推荐使用QSqlRelationalTableModel,有代理可以提供combobox对外键的编辑。
Relational Table Model示例展示了如何使用 QSqlRelationalTableModel 和 QSqlRelationalDelegate 来提供外键的支持。
六、Creating Data-Aware Forms:创建数据感知的Forms
有些时候,除了把model里的数据放到view里,还可能会放到其他widge里,比如SpinBox,comboBox,LineEditor等。
用QDataWidgetMapper可以把model中的数据映射到widget中。
QDataWidgetMapper处理一个指定一个数据库table,通过行或列的形式映射数据。因此,使用QDataWidgetMapper和SQL model就像使用其他table model一样简单。
The Books展示了如何使用QDataWidgetMapper以及一系列输入widgets的案例。
SQLite QSqlQuery的size()方法始终返回-1
https://deepinout.com/sqlite/sqlite-questions/184_sqlite_qsqlquery_size_always_returns_1.html