Qt数据库编程文档

42 篇文章 1 订阅
16 篇文章 0 订阅

SQL Programming

This overview assumes that you have at least a basic knowledge of SQL. You should be able to understand simple SELECTINSERTUPDATE, and DELETE statements. Although the QSqlTableModel class provides an interface to database browsing and editing that does not require a knowledge of SQL, a basic understanding of SQL is highly recommended. A standard text covering SQL databases is An Introduction to Database Systems (7th Ed.) by C. J. Date, ISBN 0201385902.

Topics:

Database Classes

These classes provide access to SQL databases.

QSql

Contains miscellaneous identifiers used throughout the Qt SQL library

QSqlDatabase

Represents a connection to a database

QSqlDriver

Abstract base class for accessing specific SQL databases

QSqlDriverCreator

Template class that provides a SQL driver factory for a specific driver type

QSqlDriverCreatorBase

The base class for SQL driver factories

QSqlError

SQL database error information

QSqlField

Manipulates the fields in SQL database tables and views

QSqlIndex

Functions to manipulate and describe database indexes

QSqlQuery

Means of executing and manipulating SQL statements

QSqlQueryModel

Read-only data model for SQL result sets

QSqlRecord

Encapsulates a database record

QSqlRelationalTableModel

Editable data model for a single database table, with foreign key support

QSqlResult

Abstract interface for accessing data from specific SQL databases

QSqlTableModel

Editable data model for a single database table

The SQL classes are divided into three layers:

Driver Layer

This comprises the classes QSqlDriverQSqlDriverCreator<T>, QSqlDriverCreatorBaseQSqlDriverPlugin, and QSqlResult.

This layer provides the low-level bridge between the specific databases and the SQL API layer. See SQL Database Drivers for more information.

SQL API Layer

These classes provide access to databases. Connections are made using the QSqlDatabase class. Database interaction is achieved by using the QSqlQuery class. In addition to QSqlDatabase and QSqlQuery, the SQL API layer is supported by QSqlErrorQSqlFieldQSqlIndex, and QSqlRecord.

User Interface Layer

These classes link the data from a database to data-aware widgets. They include QSqlQueryModel,QSqlTableModel, and QSqlRelationalTableModel. These classes are designed to work with Qt's model/view framework.

Note that to use any of these classes, a QCoreApplication object must have been instantiated first.

Connecting to Databases

To access a database with QSqlQuery or QSqlQueryModel, create and open one or more database connections. Database connections are normally identified by connection name, not by database name. You can have multiple connections to the same database. QSqlDatabase also supports the concept of a defaultconnection, which is an unnamed connection. When calling QSqlQuery or QSqlQueryModel member functions that take a connection name argument, if you don't pass a connection name, the default connection will be used. Creating a default connection is convenient when your application only requires one database connection.

Note the difference between creating a connection and opening it. Creating a connection involves creating an instance of class QSqlDatabase. The connection is not usable until it is opened. The following snippet shows how to create a default connection and then open it:

     QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
     db.setHostName("bigblue");
     db.setDatabaseName("flightdb");
     db.setUserName("acarlson");
     db.setPassword("1uTbSbAs");
     bool ok = db.open();

The first line creates the connection object, and the last line opens it for use. In between, we initialize some connection information, including the database name, the host name, the user name, and the password. In this case, we are connecting to the MySQL database flightdb on the host bigblue. The "QMYSQL" argument to addDatabase() specifies the type of database driver to use for the connection. The set of database drivers included with Qt are shown in the table of supported database drivers.

The connection in the snippet will be the default connection, because we don't pass the second argument toaddDatabase(), which is the connection name. For example, here we establish two MySQL database connections named "first" and "second":

QSqlDatabase firstDB = QSqlDatabase::addDatabase("QMYSQL", "first");
QSqlDatabase secondDB = QSqlDatabase::addDatabase("QMYSQL", "second");

After these connections have been initialized, open() for each one to establish the live connections. If theopen() fails, it returns false. In that case, call QSqlDatabase::lastError() to get error information.

Once a connection is established, we can call the static function QSqlDatabase::database() from anywhere with a connection name to get a pointer to that database connection. If we don't pass a connection name, it will return the default connection. For example:

     QSqlDatabase defaultDB = QSqlDatabase::database();
     QSqlDatabase firstDB = QSqlDatabase::database("first");
     QSqlDatabase secondDB = QSqlDatabase::database("second");

To remove a database connection, first close the database using QSqlDatabase::close(), then remove it using the static method QSqlDatabase::removeDatabase().

Executing SQL Statements

The QSqlQuery class provides an interface for executing SQL statements and navigating through the result set of a query.

The QSqlQueryModel and QSqlTableModel classes described in the next section provide a higher-level interface for accessing databases. If you are unfamiliar with SQL, you might want to skip directly to the next section (Using the SQL Model Classes).

Executing a Query

To execute an SQL statement, simply create a QSqlQuery object and call QSqlQuery::exec() like this:

    QSqlQuery query;
    query.exec("SELECT name, salary FROM employee WHERE salary > 50000");

The QSqlQuery constructor accepts an optional QSqlDatabase object that specifies which database connection to use. In the example above, we don't specify any connection, so the default connection is used.

If an error occurs, exec() returns false. The error is then available as QSqlQuery::lastError().

Navigating the Result Set

QSqlQuery provides access to the result set one record at a time. After the call to exec()QSqlQuery's internal pointer is located one position before the first record. We must call QSqlQuery::next() once to advance to the first record, then next() again repeatedly to access the other records, until it returns false. Here's a typical loop that iterates over all the records in order:

    while (query.next()) {
         QString name = query.value(0).toString();
         int salary = query.value(1).toInt();
         qDebug() << name << salary;
     }

The QSqlQuery::value() function returns the value of a field in the current record. Fields are specified as zero-based indexes. QSqlQuery::value() returns a QVariant, a type that can hold various C++ and core Qt data types such as intQString, and QByteArray. The different database types are automatically mapped into the closest Qt equivalent. In the code snippet, we call QVariant::toString() and QVariant::toInt() to convert variants to QString and int.

For an overview of the recommended types for use with Qt-supported Databases, please refer to this table.

You can iterate back and forth using QSqlQuery::next(), QSqlQuery::previous(), QSqlQuery::first(),QSqlQuery::last(), and QSqlQuery::seek(). The current row index is returned by QSqlQuery::at(), and the total number of rows in the result set is avaliable as QSqlQuery::size() for databases that support it.

To determine whether a database driver supports a given feature, use QSqlDriver::hasFeature(). In the following example, we call QSqlQuery::size() to determine the size of a result set of the underlying database supports that feature; otherwise, we navigate to the last record and use the query's position to tell us how many records there are.

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;      }

if you iterate through a result set only using next() and seek() with positive values, you can call QSqlQuery::setForwardOnly(true) before calling exec(). This is an easy optimization that will speed up the query significantly when operating on large result sets.

Inserting, Updating, and Deleting Records

QSqlQuery can execute arbitrary SQL statements, not just SELECTs. The following example inserts a record into a table using INSERT:

QSqlQuery query;
     query.exec("INSERT INTO employee (id, name, salary) "
                "VALUES (1001, 'Thad Beaumont', 65000)");

If you want to insert many records at the same time, it is often more efficient to separate the query from the actual values being inserted. This can be done using placeholders. Qt supports two placeholder syntaxes: named binding and positional binding. Here's an example of named binding:

    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();Here's an example of positional binding:
      QSqlQuery query;
     query.prepare("INSERT INTO employee (id, name, salary) "
                   "VALUES (?, ?, ?)");
     query.addBindValue(1001);
     query.addBindValue("Thad Beaumont");
     query.addBindValue(65000);
     query.exec();
Both syntaxes work with all database drivers provided by Qt. If the database supports the syntax natively, Qt simply forwards the query to the DBMS; otherwise, Qt simulates the placeholder syntax by preprocessing the query. The actual query that ends up being executed by the DBMS is available asQSqlQuery::executedQuery().

When inserting multiple records, you only need to call QSqlQuery::prepare() once. Then you call bindValue()or addBindValue() followed by exec() as many times as necessary.

Besides performance, one advantage of placeholders is that you can easily specify arbitrary values without having to worry about escaping special characters.

Updating a record is similar to inserting it into a table:

     QSqlQuery query;
     query.exec("UPDATE employee SET salary = 70000 WHERE id = 1003");

You can also use named or positional binding to associate parameters to actual values.

Finally, here's an example of a DELETE statement:

    QSqlQuery query;
     query.exec("DELETE FROM employee WHERE id = 1007");

Transactions

If the underlying database engine supports transactions, QSqlDriver::hasFeature(QSqlDriver::Transactions) will return true. You can use QSqlDatabase::transaction() to initiate a transaction, followed by the SQL commands you want to execute within the context of the transaction, and then eitherQSqlDatabase::commit() or QSqlDatabase::rollback(). When using transactions you must start the transaction before you create your query.

Example:

    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();

Transactions can be used to ensure that a complex operation is atomic (for example, looking up a foreign key and creating a record), or to provide a means of canceling a complex change in the middle.

 

Using the SQL Model Classes

In addition to QSqlQuery, Qt offers three higher-level classes for accessing databases. These classes areQSqlQueryModelQSqlTableModel, and QSqlRelationalTableModel.

QSqlQueryModel A read-only model based on an arbitrary SQL query.
QSqlTableModel A read-write model that works on a single table.
QSqlRelationalTableModelQSqlTableModel subclass with foreign key support.

These classes derive from QAbstractTableModel (which in turn inherits from QAbstractItemModel) and make it easy to present data from a database in an item view class such as QListView and QTableView. This is explained in detail in the Presenting Data in a Table View section.

Another advantage of using these classes is that it can make your code easier to adapt to other data sources. For example, if you use QSqlTableModel and later decide to use XML files to store data instead of a database, it is essentially just a matter of replacing one data model with another.

The SQL Query Model

QSqlQueryModel offers a read-only model based on an SQL query.

Example:

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;      }

After setting the query using QSqlQueryModel::setQuery(), you can use QSqlQueryModel::record(int) to access the individual records. You can also use QSqlQueryModel::data() and any of the other functions inherited from QAbstractItemModel.

There's also a setQuery() overload that takes a QSqlQuery object and operates on its result set. This enables you to use any features of QSqlQuery to set up the query (e.g., prepared queries).

The SQL Table Model

QSqlTableModel offers a read-write model that works on a single SQL table at a time.

Example:

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 is a high-level alternative to QSqlQuery for navigating and modifying individual SQL tables. It typically results in less code and requires no knowledge of SQL syntax.

Use QSqlTableModel::record() to retrieve a row in the table, and QSqlTableModel::setRecord() to modify the row. For example, the following code will increase every employee's salary by 10 per cent:

         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();

You can also use QSqlTableModel::data() and QSqlTableModel::setData(), which are inherited fromQAbstractItemModel, to access the data. For example, here's how to update a record using setData():

     model.setData(model.index(row, column), 75000);
     model.submitAll();

Here's how to insert a row and populate it:

     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();

Here's how to delete five consecutive rows:

     model.removeRows(row, 5);
     model.submitAll();

The first argument to QSqlTableModel::removeRows() is the index of the first row to delete.

When you're finished changing a record, you should always call QSqlTableModel::submitAll() to ensure that the changes are written to the database.

When and whether you actually need to call submitAll() depends on the table's edit strategy. The default strategy is QSqlTableModel::OnRowChange, which specifies that pending changes are applied to the database when the user selects a different row. Other strategies are QSqlTableModel::OnManualSubmit(where all changes are cached in the model until you call submitAll()) and QSqlTableModel::OnFieldChange(where no changes are cached). These are mostly useful when QSqlTableModel is used with a view.

QSqlTableModel::OnFieldChange seems to deliver the promise that you never need to call submitAll() explicitly. There are two pitfalls, though:

  • Without any caching, performance may drop significantly.
  • If you modify a primary key, the record might slip through your fingers while you are trying to populate it.

The SQL Relational Table Model

QSqlRelationalTableModel extends QSqlTableModel to provide support for foreign keys. A foreign key is a 1-to-1 mapping between a field in one table and the primary key field of another table. For example, if a booktable has a field called authorid that refers to the author table's id field, we say that authorid is a foreign key.

The screenshot on the left shows a plain QSqlTableModel in a QTableView. Foreign keys (city and country) aren't resolved to human-readable values. The screenshot on the right shows a QSqlRelationalTableModel, with foreign keys resolved into human-readable text strings.

The following code snippet shows how the QSqlRelationalTableModel was set up:

 model->setTable("employee");

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

See the QSqlRelationalTableModel documentation for details.

 

Presenting Data in a Table View

The QSqlQueryModelQSqlTableModel, and QSqlRelationalTableModel classes can be used as a data source for Qt's view classes such as QListViewQTableView, and QTreeView. In practice, QTableView is by far the most common choice, because an SQL result set is essentially a two-dimensional data structure.

A table view displaying a QSqlTableModel

The following example creates a view based on an SQL data model:

     QTableView *view = new QTableView;
     view->setModel(model);
     view->show();

If the model is a read-write model (e.g., QSqlTableModel), the view lets the user edit the fields. You can disable this by calling

 view->setEditTriggers(QAbstractItemView::NoEditTriggers);
 

You can use the same model as a data source for multiple views. If the user edits the model through one of the views, the other views will reflect the changes immediately. The Table Model example shows how it works.

View classes display a header at the top to label the columns. To change the header texts, callsetHeaderData() on the model. The header's labels default to the table's field names. For example:

     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 also has a vertical header on the left with numbers identifying the rows. If you insert rows programmatically using QSqlTableModel::insertRows(), the new rows will be marked with an asterisk (*) until they are submitted using submitAll() or automatically when the user moves to another record (assuming theedit strategy is QSqlTableModel::OnRowChange).

Inserting a row in a model

Likewise, if you remove rows using removeRows(), the rows will be marked with an exclamation mark (!) until the change is submitted.

The items in the view are rendered using a delegate. The default delegate, QItemDelegate, handles the most common data types (intQStringQImage, etc.). The delegate is also responsible for providing editor widgets (e.g., a combobox) when the user starts editing an item in the view. You can create your own delegates by subclassing QAbstractItemDelegate or QItemDelegate. See Model/View Programming for more information.

QSqlTableModel is optimized to operate on a single table at a time. If you need a read-write model that operates on an arbitrary result set, you can subclass QSqlQueryModel and reimplement flags() andsetData() to make it read-write. The following two functions make fields 1 and 2 of a query model editable:

 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;  }

 

The setFirstName() helper function is defined as follows:

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();
 }

The setLastName() function is similar. See the Query Model example for the complete source code.

Subclassing a model makes it possible to customize it in many ways: You can provide tooltips for the items, change the background color, provide calculated values, provide different values for viewing and editing, handle null values specially, and more. See Model/View Programming as well as the QAbstractItemViewreference documentation for details.

If all you need is to resolve a foreign key to a more human-friendly string, you can useQSqlRelationalTableModel. For best results, you should also use QSqlRelationalDelegate, a delegate that provides combobox editors for editing foreign keys.

Editing a foreign key in a relational table

The Relational Table Model example illustrates how to use QSqlRelationalTableModel in conjunction withQSqlRelationalDelegate to provide tables with foreign key support.

Creating Data-Aware Forms

Using the SQL models described above, the contents of a database can be presented to other model/view components. For some applications, it is sufficient to present this data using a standard item view, such asQTableView. However, users of record-based applications often require a form-based user interface in which data from a specific row or column in a database table is used to populate editor widgets on a form.

Such data-aware forms can be created with the QDataWidgetMapper class, a generic model/view component that is used to map data from a model to specific widgets in a user interface.

QDataWidgetMapper operates on a specific database table, mapping items in the table on a row-by-row or column-by-column basis. As a result, using QDataWidgetMapper with a SQL model is as simple as using it with any other table model.

The Books demonstration shows how information can be presented for easy access by usingQDataWidgetMapper and a set of simple input widgets.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值