Назад Содержание Вперед

Подключение с базе данных и выполнение SQL-запросов

Для подключения к базе данных надо указать название SQL-драйвера, например:
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "MyDB1");
Второй необязательный параметр позволяет задать имя соединения.

Затем указывается имя сервера, название базы данных, имя пользователя и пароль:

db.setHostName("localhost"); // или, например, "my1.server.ru"
db.setDatabaseName("mydb1");
db.setUserName("root");
db.setPassword("mypassword");
Если сервер использует нестандартный порт, то придётся задать и его:
db.setPort(НомерПорта);
В случае использования QODBC имя сервера не требуется, а вместо названия базы данных указывается ODBC-псевдоним (алиас).

SQLite не поддерживает авторизацию пользователей, поэтому ему требуется указать только имя файла данных. Предопределённое имя ":memory:" позволяет размещать временную базу данных в оперативной памяти.

После того, как все параметры подключения заданы, можно открыть соединение:

bool connected = db.open();
Если подключение установить не удалось, то не плохо бы узнать описание ошибки и сообщить его пользователю:
if (!connected) {
    QMessageBox::critical( // Диалог с сообщением об ошибке.
        parent,                      // Родительский виджет.
        QObject::tr("Database Error"),   // Заголовок.
        db.lastError().text());          // Текст сообщения.
    return false; // Вернуть признак неудачного подключения.
}
Если подключение установлено, то можно выполнить любой SQL-запрос, например:
QSqlQuery sql;
sql.exec("SELECT id, name, salary FROM empl WHERE salary>=1000");
или
QSqlQuery sql("SELECT id, name, salary FROM empl WHERE salary>=1000");
Здесь запрашиваются номера id, имена name и оклады salary всех работников из таблицы empl, у которых оклад не ниже 1 000. Обратите внимание, что если при создании объекта QSqlQuery указан текст запроса на языке SQL, то этот запрос сразу выполняется.

В обоих случаях в конструкторе запроса можно указать базу данных QSqlDatabase, с которой он будет работать. Но как правило, приложение открывает только одно соединение с единственной базой данных, поэтому этот параметр принимает значение по умолчанию.

Если при выполнении запроса возникла ошибка, то метод lastError() позволяет вывести на экран её описание:

if ( ! query.isActive() )
    QMessageBox::warning(
        this, tr("Database Error"),
        query.lastError().text() );
Иначе можно получить данные, которые сервер вернул в качестве результата:
while ( sql.next() ) {
    qint64 id = sql.value(0).toLongLong();
    QString name = sql.value(1).toString();
    double salary = sql.value(2).toDouble();
    // .......
}
Метод QSqlQuery::next() переводит курсор на очередную запись результирующего набора данных или возвращает false, если достигнут его конец. Метод value(номер_столбца) возвращает значение типа QVariant, которое надо преобразовать к нужному типу с помощью методов QVariant::toInt, QVariant::toLongLong, QVariant::toString, QVariant::toDouble, QVariant::toDate, QVariant::toDateTime и т.д.

Кроме next(), для навигации по набору данных можно использовать методы first(), last(), previous(), seek(int index, bool relative=false). Для увеличения быстродействия набор данных лучше сделать однонаправленным, вызвав метод QSqlQuery::setForwardOnly(true) до выполнения запроса, после этого можно использовать только next().

Метод QSqlQuery::size() возвращает число записей, полученных в результате выполнения запроса SELECT (-1, если была ошибка или если драйвер данной СУБД не поддерживает эту функцию). При выполнении SQL-запросов INSERT, UPDATE или DELETE вместо size() надо использовать метод QSqlQuery::numRowsAffected(). Чтобы узнать, возникла ли ошибка при последнем выполнении запроса, используется метод QSqlQuery::lastError(). Аналогичный метод имеет и класс QSqlDatabase. В обоих случаях возвращается экземпляр класса QSqlError. Тип ошибки можно выяснить, вызвав метод QSqlError::type(). Возможные типы ошибок: QSqlError::NoError (ошибок не было), QSqlError::ConnectionError (ошибка соединения), QSqlError::StatementError (синтаксическая ошибка в SQL-запросе), QSqlError::TransactionError (ошибка транзакции) и QSqlError::UnknownError (неизвестная ошибка).

Если требуется выполнить большое количество однотипных SQL-операторов, то эффективнее использовать запрос с параметрами:

QSqlQuery query;
sql.prepare("INSERT INTO empl (id, name, salary) "
            "VALUES (:id, :name, :salary)");
for (int i=0; i<N; i++) {
    sql.bindValue(":id", arr_id[i]);
    sql.bindValue(":name", arr_name[i]);
    sql.bindValue(":salary", arr_salary[i]);
    sql.exec();
}
или, что же самое (только параметры безымянные):
QSqlQuery query;
sql.prepare("INSERT INTO empl (id, name, salary) "
            "VALUES (?, ?, ?)");
for (int i=0; i<N; i++) {
    sql.addBindValue(arr_id[i]);
    sql.addBindValue(arr_name[i]);
    sql.addBindValue(arr_salary[i]);
    sql.exec();
}
Здесь выполняется вставка N записей, данные берутся из массивов arr_id, arr_name и arr_salary.

Если СУБД поддерживает механизм транзакций, то для начала новой транзакции используется метод

bool QSqlDatabase::transaction()
для её подтверждения надо вызвать
bool QSqlDatabase::commit()
а для отмены:
bool QSqlDatabase::rollback()
Если СУБД не поддерживает транзакций, то вызовы transaction, commit и rollback ничего не делают. С помощью метода QSqlDriver::hasFeature() можно узнать, поддерживается ли данным драйвером и СУБД та или иная функция, в том числе и транзакции:
QSqlDriver *driver = QSqlDatabase::database().driver();
if (driver->hasFeature(QSqlDriver::Transactions))
    .......
Каждое соединение с базой данных может иметь только одну активную транзакцию. Если этого недостаточно, всегда можно открыть ещё несколько соединений с той же базой данных.

Ссылку на соединение с базой данных можно получить, вызвав функцию QSqlDatabase::database(connectionName). Необязательный параметр connectionName -- это имя соединения, которое было задано при его создании с помощью QSqlDatabase::addDatabase().

По окончании работы с базой данных соединение надо закрыть: QSqlDatabase::close(). Затем можно либо открыть его снова с помощью метода open(), либо удалить из списка соединений, вызвав статический метод QSqlDatabase::removeDatabase(connectionName).


Выполнение SQL-запросов (система Windows, драйвер QODBC)


Выполнение SQL-запросов (система Linux, драйвер QMYSQL)

В листингах приведён пример программы, работающей с базой данных, а на рис. показан результат её работы в Windows и Linux.

Листинг. Выполнение SQL-запросов (файл examples-qt/db00/db00.h)

    1 #include <QtGui>
    2 
    3 class MyDialog : public QDialog {
    4     Q_OBJECT
    5 public:
    6     MyDialog(QWidget *parent=0);
    7 protected:
    8     virtual void closeEvent(QCloseEvent *event);
    9 private slots:
   10     bool start();
   11 private:
   12     QComboBox *mode;   // Режим (драйвер).
   13     QLineEdit *host;   // Хост.
   14     QLineEdit *dbname; // Имя БД.
   15     QLineEdit *user;   // Пользователь.
   16     QLineEdit *password; // Пароль.
   17     QTextEdit *scr;      // Для вывода сообщений.
   18     QPushButton *btnStart; // Кнопка 'Старт'.
   19 ;

Листинг. Выполнение SQL-запросов (файл examples-qt/db00/db00.cpp)

    1 #include <QtGui>
    2 #include <QtSql>
    3 #include <QtTest/QtTest> // Для qWait().
    4 
    5 #include "db00.h"
    6 
    7 MyDialog::MyDialog(QWidget *parent)
    8         : QDialog(parent) {
    9     QTextCodec *codec = QTextCodec::codecForName("CP1251");
   10     QTextCodec::setCodecForTr(codec);
   11     QTextCodec::setCodecForCStrings(codec);
   12     QTextCodec::setCodecForLocale(codec);
   13 
   14     setWindowFlags(Qt::Window);
   15 
   16     mode = new QComboBox(this);
   17     QStringList drivers = QSqlDatabase::drivers();
   18     drivers.removeAll("QMYSQL3");
   19     drivers.removeAll("QOCI8");
   20     drivers.removeAll("QODBC3");
   21     drivers.removeAll("QPSQL7");
   22     drivers.removeAll("QTDS7");
   23     mode->addItems(drivers);
   24 
   25     host = new QLineEdit(tr("localhost"), this);
   26     dbname = new QLineEdit(this);
   27     user = new QLineEdit(this);
   28     password = new QLineEdit(this);
   29     password->setEchoMode(QLineEdit::Password);
   30 
   31     btnStart = new QPushButton(tr("Старт"), this);
   32 
   33     scr = new QTextEdit(this);
   34     scr->setReadOnly(true);
   35 
   36     QGridLayout *layout = new QGridLayout(this);
   37     layout->addWidget(new QLabel(tr("Режим:"), this),
   38                       0, 0, Qt::AlignRight);
   39     layout->addWidget(mode, 0, 1, 1, 3);
   40 
   41     layout->addWidget(new QLabel(tr("Хост:"), this),
   42                       1, 0, Qt::AlignRight);
   43     layout->addWidget(host, 1, 1);
   44 
   45     layout->addWidget(new QLabel(tr("База данных:"), this),
   46                       1, 2, Qt::AlignRight);
   47     layout->addWidget(dbname, 1, 3);
   48 
   49     layout->addWidget(new QLabel(tr("Пользователь:"), this),
   50                       2, 0, Qt::AlignRight);
   51     layout->addWidget(user, 2, 1);
   52 
   53     layout->addWidget(new QLabel(tr("Пароль:"), this),
   54                       2, 2, Qt::AlignRight);
   55     layout->addWidget(password, 2, 3);
   56 
   57     layout->addWidget(btnStart, 3, 1, 1, 2);
   58     layout->addWidget(scr, 4, 0, 1, 4);
   59 
   60     layout->setMargin(6);
   61     layout->setSpacing(5);
   62     layout->setColumnStretch(1, 1);
   63     layout->setColumnStretch(3, 1);
   64     layout->setRowStretch(4, 1);
   65     setLayout(layout);
   66 
   67     connect(btnStart, SIGNAL(clicked()), this, SLOT(start()));
   68 }
   69 
   70 bool MyDialog::start() {
   71     scr->append(tr("Соединяюсь с базой данных..."));
   72     QSqlDatabase db = QSqlDatabase::addDatabase(
   73         mode->currentText() );
   74     db.setHostName(host->text());
   75     db.setDatabaseName(dbname->text());
   76     db.setUserName(user->text());
   77     db.setPassword(password->text());
   78     if (db.open()) {
   79         mode->setEnabled(false);
   80         host->setEnabled(false);
   81         dbname->setEnabled(false);
   82         user->setEnabled(false);
   83         password->setEnabled(false);
   84         btnStart->setEnabled(false);
   85         scr->append(tr("Соединение установлено!"));
   86     }else{
   87         scr->append(tr("Не могу соединиться: "));
   88         scr->append(db.lastError().text());
   89         return false;
   90     }
   91 
   92     QSqlQuery sql = QSqlQuery();
   93     //sql.exec(tr("SET NAMES 'cp1251'"));
   94     QStringList dbtables = db.tables(QSql::Tables);
   95     if (dbtables.contains( tr("employee"),
   96                            Qt::CaseInsensitive)) {
   97         scr->append( tr(
   98                 "Таблица \"employee\" уже существует."));
   99         sql.exec(tr("DROP TABLE employee"));
  100         if ( sql.lastError().type() == QSqlError::NoError ) {
  101             scr->append( tr(
  102                 "Удалили таблицу \"employee\" "));
  103         }else{
  104             scr->append( tr(
  105                 "Не могу удалить таблицу \"employee\":"));
  106             scr->append(sql.lastError().text());
  107             return false;
  108         }
  109     }
  110 
  111     sql.exec( tr(
  112         "create table employee ( "
  113         "  id integer PRIMARY KEY, "
  114         "  name char(30) not null, "
  115         "  born date null, "
  116         "  salary numeric(12,2), "
  117         "  married boolean NULL ) " ) );
  118     if ( sql.lastError().type() == QSqlError::NoError ) {
  119         scr->append( tr(
  120             "Создали таблицу \"employee\"."));
  121     }else{
  122         scr->append( tr(
  123                 "Не могу создать таблицу \"employee\":"));
  124         scr->append(sql.lastError().text());
  125         return false;
  126     }
  127 
  128     if (sql.prepare( tr(
  129             "INSERT INTO employee "
  130             "  VALUES (?, ?, ?, ?, ?)") ) ) {
  131         int arr_id[] = {123, 345, 501};
  132         QString arr_name[] = {tr("Винни-Пух"),
  133                               tr("Ослик Иа"),
  134                               tr("Поросёнок")};
  135         QDate arr_born[] = {QDate(1971, 12, 31),
  136                             QDate(1965, 2, 23),
  137                             QDate(1982, 6, 14)};
  138         float arr_salary[] = {1234.56f, 2345.67f, 871};
  139         int arr_married[] = {1, 0, 0};
  140 
  141         for (unsigned int i=0; i < 3; i++) {
  142             sql.bindValue(0, arr_id[i]);
  143             sql.bindValue(1, arr_name[i]);
  144             sql.bindValue(2, arr_born[i]);
  145             sql.bindValue(3, arr_salary[i]);
  146             sql.bindValue(4, arr_married[i]);
  147             sql.exec();
  148             if ( sql.lastError().type() == QSqlError::NoError ) {
  149                 scr->append( tr(
  150                     "Вставили новую запись."));
  151             }else{
  152                 scr->append( tr(
  153                     "Не могу вставить новую запись:"));
  154                 scr->append(sql.lastError().text());
  155                 return false;
  156             }
  157         }
  158     }else{
  159         scr->append( tr(
  160                 "Не могу подготовить запрос:"));
  161         scr->append(sql.lastError().text());
  162         return false;
  163     }
  164 
  165     sql.exec( tr("SELECT * FROM employee ") );
  166     if ( sql.isActive() ) {
  167         QSqlRecord rec = sql.record();
  168         scr->append( tr(
  169             "В таблице \"employee\" %1 столбцов: ")
  170             .arg(rec.count() ) );
  171 
  172         QString fields;
  173         for(int j=0; j<rec.count(); j++)
  174             fields += rec.fieldName(j) + ", ";
  175 
  176         scr->append(fields);
  177 
  178         scr->append( tr(
  179             "В таблице \"employee\" %1 записей: ")
  180             .arg(sql.size() ) );
  181 
  182         while ( sql.next() ) {
  183             int id = sql.value(0).toInt();
  184             QString name = sql.value(1).toString();
  185             QDate born = sql.value(2).toDate();
  186             double salary = sql.value(3).toDouble();
  187             bool married = sql.value(4).toBool();
  188             scr->append( tr(
  189                 "%1\t %2\t %3\t %4\t %5")
  190                 .arg(id)
  191                 .arg(name)
  192                 .arg(born.toString(tr("dd/MM/yyyy")))
  193                 .arg(salary)
  194                 .arg(married) );
  195         }
  196     }else{
  197         scr->append( tr(
  198                 "Не могу получить данные:"));
  199         scr->append(sql.lastError().text());
  200         return false;
  201     }
  202 
  203     scr->append( tr(
  204         "При закрытии окна соединение с БД будет завершено."));
  205     return true;
  206 }
  207 
  208 void MyDialog::closeEvent(QCloseEvent *event) {
  209     QSqlDatabase db = QSqlDatabase::database();
  210     if (db.isOpen()) {
  211         db.close();
  212         scr->append("--------------------------");
  213         scr->append(tr("Соединение с базой данных закрыто!"));
  214         QTest::qWait(1000);    // Ждать 1 сек.
  215     }
  216 }
  217 
  218 int main(int argc, char *argv[]) {
  219     QApplication app(argc, argv);
  220 
  221     MyDialog *mainWin = new MyDialog();
  222     mainWin->show();
  223     return app.exec();
  224 }

Перед компиляцией проекта, в котором используется модуль QtSql, надо добавить в pro-файл строку QT += sql.

Перед выполнением этой программы требуется создать какую-нибудь базу данных:

create database db1 character set utf8;

(вместо utf8 можно использовать кодировки cp1251 или koi8r); а перед использованием QODBC -- настроить ODBC-псевдоним.

 

Назад Содержание Вперед