SQL编程
这个教程假定你已经至少拥有了对于SQL的基本知识。你应该能够理解简单的SELECT,INSERT,UPDATE和DELETE语句。
虽然QSqlTableModule类提供了无需SQL就可以对数据库进行浏览和编辑的接口,但依然高度推荐你了解一下SQL。
主题:
- 数据库类
- 连接到数据库
- 执行SQL语句
- 使用SQL模型类
- 在列表视图中呈现数据
- 创建数据感知表格
数据库类
这些类提供了对于SQL数据库的访问
类名 | 描述 |
---|---|
QSql | 包含Qt SQL模块中使用的各种标识符 |
QSqlDatabase | 代表了到一个数据库的连接 |
QSqlDriver | 连接特定SQL数据库的抽象基类 |
QSqlDriverCreator | 为特定驱动程序类型提供SQL驱动程序工厂的模板类 |
QSqlDriverCreatorBase | SQL驱动工厂的基类 |
QSqlError | SQL数据库的错误信息 |
QSqlField | 操作SQL数据库表和视图中的字段 |
QSqlIndex | 用于操作和描述数据库索引的函数 |
QSqlQuery | 执行和操作SQL语句的方法 |
QSqlQueryModel | SQL结果集合的只读数据模型 |
QSqlRecord | 封装了一条数据库记录 |
QSqlRelationTableModel | 对于单个数据库表的可编辑的数据库模型,支持外键 |
QSqlResult | 访问指定SQL数据库中数据的抽象接口 |
QSqlTableModel | 对于单个数据库表的可编辑数据模型 |
这些SQL类被分为以下3层
驱动层
这包含QSqlDriver,QSqlDriverCreator,QSqlDriverCreatorBase,QSqldriverPlugin和QSqlResult这几个类。
这一层实现了特定数据库和SQL API层之间的底层桥接。
SQL API 层
这些类提供了对于数据库的访问。使用QSqlDatabase实现连接,QSqlQuery实现了与数据库的交互,
除此之外,SQL API层还被QSqlError,QSqlField,QSqlIndex和QSqlRecord所支持。
用户接口层
这些类将数据从数据库链接到数据感知的窗体,它们包QSqlQueryModel, QSqlTableModel, 和 QSqlRelationalTableModel。
这些类旨在与Qt的模型/视图框架一起使用。
注意在使用这些类之前QCoreApplication对象一定要被实例化。
连接数据库
要使用QSqlQuery或QSqlQueryModel访问数据库,请创建并打开一个或多个数据库连接。 数据库连接通常由连接名称而不是数据库名称来标识。 您可以有多个连接到同一个数据库。 QSqlDatabase还支持默认连接的概念,这是一个未命名的连接。 当调用连接名称参数的QSqlQuery或QSqlQueryModel成员函数时,如果不传递连接名称,将使用默认连接。 当应用程序只需要一个数据库连接时,创建默认连接就很方便。
注意创建连接并打开它之间的区别。 创建连接涉及创建QSqlDatabase类的实例。 连接在打开之前不可用。 以下片段显示如何创建默认连接,然后打开它:
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("bigblue");
db.setDatabaseName("flightdb");
db.setUserName("acarlson");
db.setPassword("1uTbSbAs");
bool ok = db.open();
第一行创建连接对象,最后一行打开它以供使用。 之间,我们初始化一些连接信息,包括数据库名称,主机名,用户名和密码。 在这种情况下,我们连接到主机bigblue上的MySQL数据库flightdb。 addDatabase()的“QMYSQL”参数指定用于连接的数据库驱动程序的类型。 Qt附带的数据库驱动程序集显示在支持的数据库驱动程序表中。
代码段中的连接将是默认连接,因为我们不将第二个参数传递给addDatabase(),它是连接名称。 例如,在这里我们建立了名为“first”和“second”的两个MySQL数据库连接:
QSqlDatabase firstDB = QSqlDatabase::addDatabase("QMYSQL", "first");
QSqlDatabase secondDB = QSqlDatabase::addDatabase("QMYSQL", "second");
在初始化这些连接之后,open()为每个连接建立实时连接。 如果open()失败,则返回false。 在这种情况下,调用QSqlDatabase :: lastError()来获取错误信息。
建立连接后,我们可以从任何具有连接名称的静态函数QSqlDatabase :: database()中获取指向该数据库连接的指针。 如果我们不传递连接名称,它将返回默认连接。 例如:
QSqlDatabase defaultDB = QSqlDatabase::database();
QSqlDatabase firstDB = QSqlDatabase::database("first");
QSqlDatabase secondDB = QSqlDatabase::database("second");
要删除数据库连接,首先使用QSqlDatabase :: close()关闭数据库,然后使用静态方法QSqlDatabase :: removeDatabase()将其删除。
执行SQL语句
QSqlQuery类提供了一个用于执行SQL语句并浏览查询结果集的接口。
下一节中描述的QSqlQueryModel和QSqlTableModel类提供了访问数据库的更高级别的接口。 如果您不熟悉SQL,则可能需要直接跳到下一节(使用SQL模型类)。
执行一个查询
要执行SQL语句,只需创建一个QSqlQuery对象并调用QSqlQuery :: exec():
QSqlQuery query;
query.exec("SELECT name, salary FROM employee WHERE salary > 50000");
QSqlQuery构造函数接受可选的QSqlDatabase对象,该对象指定要使用的数据库连接。 在上面的示例中,我们不指定任何连接,因此使用默认连接。
如果发生错误,exec()将返回false。 该错误随后可用作QSqlQuery :: lastError()。
浏览结果集
QSqlQuery可以一次访问结果集一个记录。 调用exec()后,QSqlQuery的内部指针位于第一个记录之前的一个位置。 我们必须调用QSqlQuery :: next()一次前进到第一条记录,然后再次next()重复访问其他记录,直到它返回false。 这是一个典型的循环,按顺序遍历所有记录:
while (query.next()) {
QString name = query.value(0).toString();
int salary = query.value(1).toInt();
qDebug() << name << salary;
}
QSqlQuery :: value()函数返回当前记录中字段的值。 字段被指定为基于零的索引。 QSqlQuery :: value()返回一个可以容纳各种C ++和核心Qt数据类型(如int,QString和QByteArray)的QVariant类型。 不同的数据库类型自动映射到最接近的Qt等价物。 在代码片段中,我们调用QVariant :: toString()和QVariant :: toInt()将变体转换为QString和int。
您可以使用QSqlQuery :: next(),QSqlQuery :: previous(),QSqlQuery :: first(),QSqlQuery :: last()和QSqlQuery :: seek()来回迭代。 当前行索引由QSqlQuery :: at()返回,结果集中的总行数可用由QSqlQuery :: size()获得(需要数据库支持)。
要确定数据库驱动程序是否支持给定的功能,请使用QSqlDriver :: hasFeature()。 在以下示例中,我们调用QSqlQuery :: size()来确定底层数据库支持该特性的结果集的大小; 否则,我们导航到最后一个记录,并使用查询的位置告诉我们有多少条记录
QSqlQuery query;
int numRows;
query.exec("SELECT name, salary FROM employee WHERE salary > 50000");
QSqlDatabase defaultDB = QSqlDatabase::database();
if (defaultDB.driver()->hasFeature(QSqlDriver::QuerySize)) {
numRows = query.size();
} else {
// this can be very slow
query.last();
numRows = query.at() + 1;
}
如果仅使用next()和seek()为正值迭代结果集,则可以在调用exec()之前调用QSqlQuery :: setForwardOnly(true)。 这是一个简单的优化,可以在大型结果集上运行时加快查询速度。
插入,更新和删除记录
QSqlQuery可以执行任意SQL语句,而不只是SELECT。 以下示例使用INSERT将记录插入到表中:
QSqlQuery query;
query.exec("INSERT INTO employee (id, name, salary) "
"VALUES (1001, 'Thad Beaumont', 65000)");
如果要同时插入多个记录,将查询与要插入的实际值分开是非常有效的。 这可以使用占位符进行。 Qt支持两个占位符语法:命名绑定和位置绑定。 以下是一个命名绑定的例子:
QSqlQuery query;
query.prepare("INSERT INTO employee (id, name, salary) "
"VALUES (:id, :name, :salary)");
query.bindValue(":id", 1001);
query.bindValue(":name", "Thad Beaumont");
query.bindValue(":salary", 65000);
query.exec();
这里是位置绑定
QSqlQuery query;
query.prepare("INSERT INTO employee (id, name, salary) "
"VALUES (?, ?, ?)");
query.addBindValue(1001);
query.addBindValue("Thad Beaumont");
query.addBindValue(65000);
query.exec();
除了性能外,占位符的一个优点是您可以轻松地指定任意值,而无需担心转义特殊字符。
更新记录和插入记录很像:
QSqlQuery query;
query.exec("UPDATE employee SET salary = 70000 WHERE id = 1003");
您任然可以使用命名或位置绑定将参数与实际值相关联。
最后是删除语句:
QSqlQuery query;
query.exec("DELETE FROM employee WHERE id = 1007");
事务
如果底层数据库引擎支持事务,则QSqlDriver :: hasFeature(QSqlDriver :: Transactions)将返回true。 您可以使用QSqlDatabase :: transaction()来启动事务,其次是要在事务上下文中执行的SQL命令,然后是QSqlDatabase :: commit()或QSqlDatabase :: rollback()。 使用事务时,您必须先创建事务,然后再创建查询。
QSqlDatabase::database().transaction();
QSqlQuery query;
query.exec("SELECT id FROM employee WHERE name = 'Torild Halvorsen'");
if (query.next()) {
int employeeId = query.value(0).toInt();
query.exec("INSERT INTO project (id, name, ownerid) "
"VALUES (201, 'Manhattan Project', "
+ QString::number(employeeId) + ')');
}
QSqlDatabase::database().commit();
可以使用事务来确保复杂操作是原子的(例如,查找外键和创建记录),或者提供一种取消中间复杂更改的方法。
使用SQL模型类
除了QSqlQuery,Qt还提供了3个高级的类来访问数据库。
这三个类分别是:QSqlQueryModel,QSqlTableModel和QSqlRelationTableModel。
这些类派生自QAbstractTableModel(它反过来继承自QAbstractItemModel),并且可以轻松地在诸如QListView和QTableView之类的项目视图类中的数据库中呈现数据。 这在“在表视图中的呈现数据”一节中有详细的说明。
使用这些类的另一个优点是它可以使您的代码更容易适应其他数据源。 例如,如果您使用QSqlTableModel,然后决定使用XML文件来存储数据而不是数据库,那么本质上只是将另一个数据模型替换为另一个数据模型。
SQL查询模型
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;
}
使用QSqlQueryModel :: setQuery()设置查询后,可以使用QSqlQueryModel :: record(int)访问各个记录。 您还可以使用QSqlQueryModel :: data()以及从QAbstractItemModel继承的任何其他函数。
还有一个setQuery()重载,它接受一个QSqlQuery对象并对其结果集进行操作。 这使您可以使用QSqlQuery的任何功能来设置查询(例如,准备好的查询)。
SQL Table模型
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;
}
QSqlTableModel是用于导航和修改单个SQL表的QSqlQuery的高级替代。 它通常导致较少的代码,并且不需要SQL语法的知识。
使用QSqlTableModel :: record()来检索表中的一行,并使用QSqlTableModel :: setRecord()修改行。 例如,以下代码将每位员工的工资增加10%:
for (int i = 0; i < model.rowCount(); ++i) {
QSqlRecord record = model.record(i);
double salary = record.value("salary").toInt();
salary *= 1.1;
record.setValue("salary", salary);
model.setRecord(i, record);
}
model.submitAll();
您还可以使用从QAbstractItemModel继承的QSqlTableModel :: data()和QSqlTableModel :: setData()来访问数据。 例如,以下是使用setData()更新记录的方法:
model.setData(model.index(row, column), 75000);
model.submitAll();
以下是插入行并填充它的方法:
model.insertRows(row, 1);
model.setData(model.index(row, 0), 1013);
model.setData(model.index(row, 1), "Peter Gordon");
model.setData(model.index(row, 2), 68500);
model.submitAll();
以下是删除连续五行的方法:
model.removeRows(row, 5);
model.submitAll();
QSqlTableModel :: removeRows()的第一个参数是要删除的第一行的索引。
完成更改记录后,应始终调用QSqlTableModel :: submitAll()以确保将更改写入数据库。
什么时候和你是否真的需要调用submitAll()取决于表的编辑策略。 默认策略是QSqlTableModel :: OnRowChange,它指定当用户选择不同的行时,挂起的更改将应用于数据库。 其他策略是QSqlTableModel :: OnManualSubmit(其中所有更改都缓存在模型中,直到调用submitAll())和QSqlTableModel :: OnFieldChange(其中没有更改被缓存)。 当QSqlTableModel与视图一起使用时,这些功能非常有用。
SQL Relational Table模型
QSqlRelationalTableModel扩展了QSqlTableModel以提供对外键的支持。 外键是一个表中的字段与另一个表的主键字段之间的1对1映射。 例如,如果book表有一个称为authorid的字段,该字段指的是author表的id字段,则表示authorid是外键。
以下代码片段显示了如何设置QSqlRelationalTableModel:
model->setTable("employee");
model->setRelation(2, QSqlRelation("city", "id", "name"));
model->setRelation(3, QSqlRelation("country", "id", "name"));
有关详细信息,请参阅QSqlRelationalTableModel文档。
在Table 视图中呈现数据
QSqlQueryModel,QSqlTableModel和QSqlRelationalTableModel类可以用作Qt视图类的数据源,例如QListView,QTableView和QTreeView。 实际上,QTableView是迄今为止最常见的选择,因为SQL结果集本质上是一个二维的数据结构。
下面的示例创建了一个基于SQL数据模型的视图:
QTableView *view = new QTableView;
view->setModel(model);
view->show();
如果模型是读写模型(例如,QSqlTableModel),则视图允许用户编辑字段。 您可以通过调用如下函数来禁用此功能
view->setEditTriggers(QAbstractItemView::NoEditTriggers);
您可以使用与多个视图相同的型号作为数据源。 如果用户通过其中一个视图编辑模型,其他视图将立即反映更改。 表模型示例显示了它的工作原理。
视图类显示顶部的标题以标记列。 要更改标题文本,请在模型上调用setHeaderData()。 标题的标签默认为表的字段名称。 例如:
model->setHeaderData(0, Qt::Horizontal, QObject::tr("ID"));
model->setHeaderData(1, Qt::Horizontal, QObject::tr("Name"));
model->setHeaderData(2, Qt::Horizontal, QObject::tr("City"));
model->setHeaderData(3, Qt::Horizontal, QObject::tr("Country"));
QTableView还有一个垂直标题在左边,数字标识行。 如果使用QSqlTableModel :: insertRows()以编程方式插入行,则新行将使用星号(*)进行标记,直到它们使用submitAll()提交,或者当用户移动到另一条记录时自动(假定编辑策略为QSqlTableModel::OnRowChange)。
同样,如果您使用removeRows()删除行,行将被标记有感叹号(!),直到更改被提交。
视图中的项目使用委托呈现。 默认代理(QItemDelegate)能够处理最常见的数据类型(int,QString,QImage等)。 当用户开始在视图中编辑项目时,代理还负责提供编辑器部件(例如,组合框)。 您可以通过对QAbstractItemDelegate或QItemDelegate进行子类化来创建自己的代理。 有关详细信息,请参阅模型/视图编程。
QSqlTableModel被优化为一次在单个表上操作。 如果需要一个在任意结果集上运行的读写模型,那么可以对QSqlQueryModel进行子类化,并重新实现flags()和setData()以使其读写。 以下两个功能使查询模型的字段1和2可编辑:
Qt::ItemFlags EditableSqlModel::flags(
const QModelIndex &index) const
{
Qt::ItemFlags flags = QSqlQueryModel::flags(index);
if (index.column() == 1 || index.column() == 2)
flags |= Qt::ItemIsEditable;
return flags;
}
bool EditableSqlModel::setData(const QModelIndex &index, const QVariant &value, int /* role */)
{
if (index.column() < 1 || index.column() > 2)
return false;
QModelIndex primaryKeyIndex = QSqlQueryModel::index(index.row(), 0);
int id = data(primaryKeyIndex).toInt();
clear();
bool ok;
if (index.column() == 1) {
ok = setFirstName(id, value.toString());
} else {
ok = setLastName(id, value.toString());
}
refresh();
return ok;
}
setFirstName()辅助函数定义如下:
bool EditableSqlModel::setFirstName(int personId, const QString &firstName)
{
QSqlQuery query;
query.prepare("update person set firstname = ? where id = ?");
query.addBindValue(firstName);
query.addBindValue(personId);
return query.exec();
}
如果您需要的是将外键解析为更加人性化的字符串,则可以使用QSqlRelationalTableModel。 为了获得最佳效果,您还应该使用QSqlRelationalDelegate,该代理提供组合框编辑器来编辑外键。
创建数据感知窗体
使用上述SQL模型,数据库的内容可以呈现给其他模型/视图组件。 对于某些应用程序,使用标准项目视图(如QTableView)来呈现此数据就足够了。 然而,基于记录的应用程序的用户通常需要基于表单的用户界面,其中来自数据库表中的特定行或列的数据用于填充表单上的编辑器部件。
可以使用QDataWidgetMapper类创建此类数据感知窗体,该类是用于将模型中的数据映射到用户界面中的特定部件的通用模型/视图组件。
QDataWidgetMapper对特定的数据库表进行操作,逐行或逐列地映射表中的项目。 因此,将QDataWidgetMapper与SQL模型一起使用与使用任何其他表模型一样简单。