1、数据库连接
为使用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数据库。addDatabase()的“QMYSQL”参数指定了连接使用的数据库驱动类型。Qt支持的数据库驱动类型见表suported database drivers.
示例代码中的连接为默认连接。因为我们没有指定addDatabase()函数的第二个参数,该参数指定了连接名称。例如下面的代码中,我们创建了两个MYSQL数据库连接分别名为“first”和“second”。
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()移除连接。
2、执行SQL语句
QSqlQuery类提供接口用于执行SQL语句并浏览查询的结果集。
下一节要介绍的QSqlQueryModel和QSqlTableModel类提供了访问数据库的高级接口。如果你对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()返回一个QVariant。不同的数据库数据类型可以自动映射到与Qt环境最接近的数据类型。
可以使用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();
两种语法都可以用于Qt支持的所有数据库驱动。如果数据库原生地支持这种语法,Qt将只是简单地把查询送到DBMS。否则,Qt将对查询进行预处理来模拟占位符语法。在被DBMS执行后结束的实际的查询将会像QSqlQuery::executedQuery()一样可用。
当插入多个记录时,只需要调用一次QSqlQuery::prepare()。可以多次调用bindValue()和addBindValue()添加记录行,之后调用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::commint()或者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();
事务可以用来确保一个复杂操作的原子执行,或者提供一种在执行中取消一个复杂操作的方法。
3、使用SQL Model类
除了QSqlQuery,Qt提供了3种高级的类用于访问数据库:QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel。
QSqlQueryModel:基于任意SQL查询的一个只读模式。
QSqlTableModel:工作于一个单独表的读写模式。
QSqlRelationalTableModel:一个支持外键的QSqlTableModel子类。
这些类继承自QAbstractTableModel,可以方便地将数据库中的数据在一个项视图类如QListView和QTableView中展现。
使用这些类的另一个优点是可以使代码更简单地适用于其他的数据源。例如,如果你使用QSqlTableModel,之后决定使用XML文件来存储数据代替数据库,将之需要替换数据模型即可。
SQL Query Model
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;
}
QSqlTableModel
QSqlTableModel是一个高级的选择用于浏览和更改独立的SQL表。需要更少的代码,并基本不需要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();
可以使用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();
下面是一个删除5个连续行的例子:
model.removeRows(row, 5);
model.submitAll();
QSqlTableModel::removeRows()的第一个参数是要删除的数据行的第一行的索引。
当完成更改记录时,可以使用QSqlTableModel::submitAll()来确保更改被写入到数据库中。
何时调用和是否需要调用submitAll()取决于数据表的编辑策略。默认的策略是QSqlTableModel::OnRowChange,指定了当用户选择了一个不同行时执行对数据行的更改。其他策略QSqlTableModel::OnManualSubmint(当调用submintAll()时执行更改)、QSqlTableModel::OnFieldCHange(不对更改进行缓存)。
QSqlTableModel::OnFieldChange看起来像是不需要用户执行submitAll()。它有两个缺陷:
没有缓存,性能将会明显下降。
如果更改了主键,记录将会在你试图填充时丢失。
SQL Relational Table Model
QSqlRelationalTableModel扩展了QSqlTableModel,它支持外键。外键是一个表中的字段与另一个表的主键字段之间的一对一映射。例如:如果一个book表有一个称为authorid的字段标识了author表的id字段,我们称authorid为一个外键。
上方的截图在一个QTableView中显示了一个QSqlTableModel。外键(city和country)不能被直接解读。下方的截图中显示了一个QSqlRelationalTableModel,外键被解析为可直接解读的字符串。
model->setTable("employee");
model->setRelation(2, QSqlRelation("city", "id", "name"));
model->setRelation(3, QSqlRelation("country", "id", "name"));
关于QSqlRelationalTableModel::setRelation(int column,const QSqlRelation &relation)的解释:
功能:设置表的column列为外键,外键关系为relation。
QSqlRelation(const QString & tableName, const QString & indexColumn, const QString & displayColumn)
tableName,参照表的名称
indexColumn,索引列
displayColunm,显示列
4、在表视图中显示数据
QSqlQueryModel、QSqlTableModel和QSqlRelationalTableModel类可以用作Qt视图类(如QListView, QTableView和 QTreeView)的数据源。通常使用QTableView,因为SQL结果集通常为二维数据结构。
下面是创建一个基于SQL数据模型的视图:
QTableView *view = new QTableView;
view->setModel(model);
view->show();
如果模型是读写模型,视图将允许用户编辑字段内容。可以通过调用 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()更新数据,或当用户移动到另一行。
相似的,如果使用removeRows()移除了某行,行将标记为“!”直到更改被接受。
视图中的项使用一个托管进行处理。默认的托管QItemDelegate可以处理大多数数据类型。当用户开始在视图中编辑一个项时,托管负责提供编辑窗体。你可以通过子类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();
}
setLastName()函数也类似。
可以通过多种方式子类化一个模型:可以提供数据项的tooltips,更改背景颜色,提供数据计算,提供不同的值用于显示和编辑,处理空值等等。
如果你的需求只是解析一个外键,可以使用QSqlRelationalTableModel。最好的方法是同时使用QSqlRelationalDelegate,提供外键的组合编辑框。