Books示例说明了Qt中SQL类如何被Model/View框架使用,使用数据库中存储的信息,创建丰富的用户界面。
首先介绍使用SQL我们需要了解的类:
1.QSqlDatabase:
QSqlDatabase类表示与数据库的连接。
QSqlDatabase类提供了一个通过连接访问数据库的接口。 QSqlDatabase的一个实例表示连接。连接通过驱动(驱动就是QSqlDriver派生类)访问数据库。或者,你自己子类化QSqlDriver,编写自己的数据库驱动。
通过调用静态addDatabase()函数创建一个连接。addDatabase()函数需要2个参数,第一个指定的驱动,第二个连接名称。Qt支持的驱动如下:
驱动 | 数据库 |
---|---|
QDB2 | IBM DB2 (7.1 或更新版本) |
QIBASE | Borland InterBase |
QMYSQL | MySQL |
QOCI | Oracle Call Interface Driver |
QODBC | Open Database Connectivity (ODBC) – Microsoft SQL Server 及其它兼容 ODBC 的数据库 |
QPSQL | PostgreSQL (7.3 或更新版本) |
QSQLITE2 | SQLite 2 |
QSQLITE | SQLite 3 |
QSYMSQL | 针对 Symbian 平台的SQLite 3 |
QTDS | Sybase Adaptive Server (自 Qt 4.7 起废除) |
连接通过设定的连接名称区分,可以多个连接连接到同一个数据库。 QSqlDatabase还支持默认连接的概念,连接未命名就是默认连接。 要创建默认连接,调用addDatabase()时不要传递连接名参数。随后,当您调用任何使用连接名称参数的静态成员函数时,如果不传递连接名称参数,则假定为默认连接。以下代码段显示了如何创建和打开与PostgreSQL数据库的默认连接:
QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL"); db.setHostName("acidalia"); db.setDatabaseName("customdb"); db.setUserName("mojito"); db.setPassword("J0a1m8"); bool ok = db.open();
创建QSqlDatabase对象后,使用setDatabaseName(),setUserName(),setPassword(),setHostName(),setPort()和setConnectOptions())设置连接参数。连接初始状态是不可用的,直到你调用open()函数激活与数据库的物理连接。
上面定义的连接将是默认连接,因为我们没有给addDatabase()提供连接名称。 随后,您可以通过调用没有连接名称参数的database()来获取默认连接:
QSqlDatabase db = QSqlDatabase::database();
QSqlDatabase是一个value class。通过QSqlDatabase实例改变一个连接,会影响其他代表同一个连接的实例。所以使用cloneDatabase()函数创建一个独立的连接,修改这个独立连接而不影响其他连接。
如果你创建多个数据库连接,请在调用addDatabase()时为每个连接指定唯一的连接名称。使用具有连接名称的database()来获取该连接。使用带有连接名称的removeDatabase()来删除连接。如果您尝试删除由其他QSqlDatabase对象引用的连接,QSqlDatabase将输出警告。 使用contains()来查看给定的连接名称是否在连接列表中。
一旦建立连接,您可以调用tables()获取数据库中的表的列表,调用primaryIndex()获取表的主索引,并调用record()获取有关表的字段的元信息。
注意:不推荐使用QSqlDatabase :: exec()。 推荐用QSqlQuery :: exec()。
如果驱动程序支持事务,请使用transaction()来启动一个事务,commit()或rollback()来完成它。使用hasFeature()来询问驱动程序是否支持事务。注意:使用事务时,必须先创建事务,然后再创建查询。
如果发生错误,lastError()将返回有关它的信息。
使用drivers()获取可用的SQL驱动程序的名称。 使用isDriverAvailable()检查特定驱动程序的存在。 如果您创建了自己的自定义驱动程序,则必须使用registerSqlDriver()注册它。
2.QSqlQuery
QSqlQuery类提供了一种执行和操作SQL语句的方法。
QSqlQuery封装了在QSqlDatabase中执行的SQL操作。它可以用于执行DML(数据操作语言)语句,如SELECT,INSERT,UPDATE和DELETE以及DDL(数据定义语言)语句,如CREATE TABLE。 它也可以用于执行不是标准SQL的数据库特定的命令(例如PostgreSQL数据库 SET DATESTYLE = ISO)。
成功执行SQL语句将query的状态设置为active,以使isActive()返回true。否则query的状态设置为inactive。在任一情况下,执行新的SQL语句时,query位于无效记录上。 必须将active query导航到有效的记录(isValid()返回true)才能检索到值。
对于某些数据库,当active query是SELECT语句时,当你调用commit()和rollback()函数处理事务时,会失败。当active query是SELECT语句时,必须调用finish()和clear()函数,使得query变成incative,再调用事务。
使用以下函数执行导航记录:
next()
previous()
first()
last()
seek()
这些函数允许程序员向前,向后或任意地移动query返回的记录。如果您仅需要前进的结果(例如,使用next()),则可以使用setForwardOnly(),这将节省大量内存开销并提高某些数据库的性能。一旦active query位于有效的记录上,可以使用value()检索数据。 所有从SQL端传送过来的数据都是QVariant类型。
举例:
QSqlQuery query("SELECT country FROM artist"); while (query.next()) { QString country = query.value(0).toString(); doSomething(country); }
要获得查询返回的数据,请使用value(int)。字段从0开始,比如"SELECT country FROM artist"只有一个字段。我们使用query.value(0)就可以获得QVariant类型的值。"SELECT forename, surname FROM people;" 此时forename字段0,surname 字段1.我们要获得surname的值query.value(1).toString()。所以使用select *是不建议的,因为你无法确定字段的顺序。
如果我们硬是要使用select *怎么办?先调用record()函数获得整个查询的记录,然后调用indexOf()函数,从记录中查找字段的位置。
QSqlQuery query("SELECT * FROM artist"); int fieldNo = query.record().indexOf("country"); while (query.next()) { QString country = query.value(fieldNo).toString(); doSomething(country); }
QSqlQuery支持预查询执行和占位符参数绑定。某些数据库不支持这些功能,因此对于这些数据库,Qt会模拟所需的功能。例如,Oracle和ODBC驱动程序具有预查询支持,Qt利用它; 但是对于不支持这种操作的数据库,Qt自身实现了这个特征。例如:在查询操作时,用占位符替换实际的值,使用numRowsAffected()函数获得非SELECT查询影响的行数。使用size()函数获得SELECT语句查询的行数。
Oracle数据库通过使用冒号语法来标识占位符,例如 :name。 ODBC只是使用?字符来标识占位符。Qt两种语法都支持,但是限制是不能混合使用。
您可以通过boundValues()函数由一个变量(QMap<QString, QVariant>)获得所有字段的值。
下面我们将介绍4中不同的占位符参数绑定方法和1中绑定值到存储过程的示例:
1.通过字段的名称绑定。
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();
2.通过字段的位置绑定
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();
3.通过字段位置绑定,占位符由 :something 换成 ?
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();
4.通过字段位置绑定, addBindValue()函数和查query语句中占位符的位置一致。
QSqlQuery query; query.prepare("INSERT INTO person (id, forename, surname) " "VALUES (?, ?, ?)"); query.addBindValue(1001); query.addBindValue("Bart"); query.addBindValue("Simpson"); query.exec();
绑定值到存储过程。
下面的代码调用一个叫做AsciiToInt()的存储过程。第一个参数字符,第二个参数存储过程的返回值。
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
QSql::Out 表示绑定值从数据库获得数据。
请注意,未绑定的参数将返回其本身的值。
存储过程使用return语句返回值或者返回多个结果集,Qt并不是完全支持。
注意:在创建QSqlQuery之前,必须加载SQL驱动程序并打开连接。 另外,执行query操作时,连接必须保持打开; 否则,QSqlQuery的行为是未定义的。
3.QSqlError
QSqlError类提供SQL数据库错误信息。
QSqlError对象可以提供数据库明确的错误信息。driverText()返回数据库驱动得到的错误信息,databaseText()返回数据库得到的错误消息(两者合并就是text()),number()返回错误数量,type()返回错误类型。 这些函数都具有设置器,以便您可以从自己的类创建和返回QSqlError对象,例如从你自定义SQL驱动程序。